diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2009-12-29 15:46:12 -0800 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2009-12-29 15:46:12 -0800 |
commit | b27a3e8da39484d8a02d3b9c1e4dc3cb60ddcce7 (patch) | |
tree | 372a4af6df43eb8bed19c11e55b2f70907d60507 | |
parent | ada895e8cac855a2f248aafdb92457365f062d07 (diff) | |
parent | b354496bda901cb0af499d6f3dff17a96a834a67 (diff) | |
download | rails-b27a3e8da39484d8a02d3b9c1e4dc3cb60ddcce7.tar.gz rails-b27a3e8da39484d8a02d3b9c1e4dc3cb60ddcce7.tar.bz2 rails-b27a3e8da39484d8a02d3b9c1e4dc3cb60ddcce7.zip |
Merge branch 'master' of git://github.com/mikel/rails into mail
Conflicts:
actionmailer/lib/action_mailer.rb
340 files changed, 6374 insertions, 5815 deletions
diff --git a/.gitignore b/.gitignore index 70b7c0057a..9a65e4996f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,16 +7,9 @@ activerecord/doc actionpack/doc actionmailer/doc activesupport/doc -activemodel/pkg -activeresource/pkg -activerecord/pkg -actionpack/pkg activemodel/test/fixtures/fixture_database.sqlite3 -actionmailer/pkg -activesupport/pkg actionpack/test/tmp activesupport/test/fixtures/isolation_test -railties/pkg railties/test/500.html railties/test/fixtures/tmp railties/test/initializer/root/log @@ -31,3 +24,4 @@ railties/guides/output bin vendor/gems/ railties/tmp +pkg diff --git a/.gitmodules b/.gitmodules index 20ed3ed9c9..d0be7ff194 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "arel"] path = arel url = git://github.com/rails/arel.git -[submodule "rack-mount"] - path = rack-mount - url = git://github.com/rails/rack-mount.git +[submodule "rack"] + path = rack + url = git://github.com/rails/rack.git @@ -1,11 +1,17 @@ gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" -gem "rails", "3.0.pre", :vendored_at => "railties" +gem "rails", "3.0.pre", :path => "railties" %w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib| - gem lib, '3.0.pre', :vendored_at => lib + gem lib, '3.0.pre', :path => lib end +# AS +gem "i18n", ">= 0.3.0" + +# AM +gem "mail", ">= 1.4.2" + # AR gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git" gem "sqlite3-ruby", ">= 1.2.5" @@ -13,7 +19,8 @@ gem "pg", ">= 0.8.0" gem "mysql", ">= 2.8.1" # AP -gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git" +gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git" +gem "rack-test", "0.5.3" gem "RedCloth", ">= 4.2.2" if ENV['CI'] @@ -30,3 +37,5 @@ if ENV['CI'] gem "test-unit", ">= 2.0.5" end end + +disable_system_gems @@ -1,9 +1,10 @@ require 'rake' require 'rake/rdoctask' +require 'rake/gempackagetask' env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD'] -PROJECTS = %w(activesupport actionpack actionmailer activeresource activerecord activemodel railties) +PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties) Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path| require version_path @@ -23,11 +24,21 @@ task :default => %w(test test:isolated) end end +spec = eval(File.read('rails.gemspec')) + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.gem_spec = spec +end + task :install => :gem do + system %(cd arel && gem build arel.gemspec && gem install arel-0.2.pre.gem --no-ri --no-rdoc --ignore-dependencies) + system %(cd rack && rake gem VERSION=1.0.2.pre && gem install rack-1.0.2.pre.gem --no-ri --no-rdoc --ignore-dependencies) (PROJECTS - ["railties"]).each do |project| + puts "INSTALLING #{project}" system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") end - system("gem install railties/pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") + system("gem install railties/pkg/railties-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") + system("gem install pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") end desc "Generate documentation for the Rails framework" diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 9b07686b64..dc2d5f7314 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,10 +1,17 @@ *Mail Integration +* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no +reversing of ordering takes place. The default order now is text/plain, then text/enriched, then +text/html and then any other part that is not one of these three. + * Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc * Every part of a Mail object returns an object, never a string. So Mail.body returns a Mail::Body - class object, need to call #encoded or #decoded to get the string you want + class object, need to call #encoded or #decoded to get the string you want. + +* By default, a field will return the #decoded value when you send it :to_s and any object that + is a container (like header, body etc) will return #encoded value when you send it :to_s * Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 1a7ece5068..6c19371514 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -23,7 +23,6 @@ task :default => [ :test ] Rake::TestTask.new { |t| t.libs << "test" t.pattern = 'test/*_test.rb' - t.verbose = true t.warning = true } diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 0ca08e4d1f..55ddbb24f4 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -21,36 +21,27 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib" -$:.unshift(actionpack_path) if File.directory?(actionpack_path) +actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__) +$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path) + require 'action_controller' require 'action_view' module ActionMailer - def self.load_all! - [Base, Part, ::Text::Format, ::Net::SMTP] - end - - autoload :AdvAttrAccessor, 'action_mailer/adv_attr_accessor' - autoload :DeprecatedBody, 'action_mailer/deprecated_body' - autoload :Base, 'action_mailer/base' - autoload :DeliveryMethod, 'action_mailer/delivery_method' - autoload :Part, 'action_mailer/part' - autoload :PartContainer, 'action_mailer/part_container' - autoload :Quoting, 'action_mailer/quoting' - autoload :TestCase, 'action_mailer/test_case' - autoload :TestHelper, 'action_mailer/test_helper' - + extend ::ActiveSupport::Autoload + + autoload :AdvAttrAccessor + autoload :Base + autoload :DeliveryMethod + autoload :DeprecatedBody + autoload :MailHelper + autoload :Quoting + autoload :TestCase + autoload :TestHelper end module Text - autoload :Format, 'action_mailer/vendor/text_format' -end + extend ActiveSupport::Autoload -module Net - autoload :SMTP, 'net/smtp' + autoload :Format, 'action_mailer/vendor/text_format' end - -autoload :MailHelper, 'action_mailer/mail_helper' - -require 'mail' diff --git a/actionmailer/lib/action_mailer/adv_attr_accessor.rb b/actionmailer/lib/action_mailer/adv_attr_accessor.rb index e77029afdd..be6b1feca9 100644 --- a/actionmailer/lib/action_mailer/adv_attr_accessor.rb +++ b/actionmailer/lib/action_mailer/adv_attr_accessor.rb @@ -1,29 +1,25 @@ module ActionMailer module AdvAttrAccessor #:nodoc: - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods #:nodoc: - def adv_attr_accessor(*names) - names.each do |name| - ivar = "@#{name}" + def adv_attr_accessor(*names) + names.each do |name| + ivar = "@#{name}" - define_method("#{name}=") do |value| - instance_variable_set(ivar, value) + class_eval <<-ACCESSORS, __FILE__, __LINE__ + 1 + def #{name}=(value) + #{ivar} = value end - define_method(name) do |*parameters| - raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1 - if parameters.empty? - if instance_variable_names.include?(ivar) - instance_variable_get(ivar) - end + def #{name}(*args) + raise ArgumentError, "expected 0 or 1 parameters" unless args.length <= 1 + if args.empty? + #{ivar} if instance_variable_names.include?(#{ivar.inspect}) else - instance_variable_set(ivar, parameters.first) + #{ivar} = args.first end end - end + ACCESSORS + + self.protected_instance_variables << ivar if self.respond_to?(:protected_instance_variables) end end end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 3639992ce9..aea2498d4d 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,9 +1,9 @@ require 'active_support/core_ext/class' +require 'mail' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. # - # # = Mailer Models # # To use Action Mailer, you need to create a mailer model. @@ -22,7 +22,8 @@ module ActionMailer #:nodoc: # bcc ["bcc@example.com", "Order Watcher <watcher@example.com>"] # from "system@example.com" # subject "New account information" - # body :account => recipient + # + # @account = recipient # end # end # @@ -42,13 +43,6 @@ module ActionMailer #:nodoc: # address. Setting this is useful when you want delivery notifications sent to a different address than # the one in <tt>from</tt>. # - # The <tt>body</tt> method has special behavior. It takes a hash which generates an instance variable - # named after each key in the hash containing the value that that key points to. - # - # So, for example, <tt>body :account => recipient</tt> would result - # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the - # view. - # # # = Mailer views # @@ -68,7 +62,12 @@ module ActionMailer #:nodoc: # You can even use Action Pack helpers in these views. For example: # # You got a new note! - # <%= truncate(note.body, 25) %> + # <%= truncate(@note.body, 25) %> + # + # If you need to access the subject, from or the recipients in the view, you can do that through mailer object: + # + # You got a new note from <%= mailer.from %>! + # <%= truncate(@note.body, 25) %> # # # = Generating URLs @@ -250,29 +249,22 @@ module ActionMailer #:nodoc: # <tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client # and appear last in the mime encoded message. You can also pick a different order from inside a method with # +implicit_parts_order+. - class Base - include AdvAttrAccessor, Quoting + class Base < AbstractController::Base + include Quoting + extend AdvAttrAccessor - include AbstractController::RenderingController + include AbstractController::Rendering include AbstractController::LocalizedCache include AbstractController::Layouts - include AbstractController::Helpers - helper MailHelper - if Object.const_defined?(:ActionController) - include ActionController::UrlWriter - end + helper ActionMailer::MailHelper + include ActionController::UrlWriter include ActionMailer::DeprecatedBody private_class_method :new #:nodoc: - class_inheritable_accessor :view_paths - self.view_paths = [] - - cattr_accessor :logger - @@raise_delivery_errors = true cattr_accessor :raise_delivery_errors @@ -291,10 +283,16 @@ module ActionMailer #:nodoc: @@default_mime_version = "1.0" cattr_accessor :default_mime_version - @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ] + # This specifies the order that the parts of a multipart email will be. Usually you put + # text/plain at the top so someone without a MIME capable email reader can read the plain + # text of your email first. + # + # Any content type that is not listed here will be inserted in the order you add them to + # the email after the content types you list here. + @@default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ] cattr_accessor :default_implicit_parts_order - @@protected_instance_variables = [] + @@protected_instance_variables = %w(@parts @mail) cattr_reader :protected_instance_variables # Specify the BCC addresses for the message @@ -344,57 +342,17 @@ module ActionMailer #:nodoc: # have multiple mailer methods share the same template. adv_attr_accessor :template - # The mail and action_name instances referenced by this mailer. - attr_reader :mail, :action_name - - # Where the response body is stored. - attr_internal :response_body - # Override the mailer name, which defaults to an inflected version of the # mailer's class name. If you want to use a template in a non-standard # location, you can use this to specify that location. - attr_writer :mailer_name + adv_attr_accessor :mailer_name - def mailer_name(value = nil) - if value - @mailer_name = value - else - @mailer_name || self.class.mailer_name - end - end + # Expose the internal mail + attr_reader :mail # Alias controller_path to mailer_name so render :partial in views work. alias :controller_path :mailer_name - # Add a part to a multipart message, with the given content-type. The - # part itself is yielded to the block so that other properties (charset, - # body, headers, etc.) can be set on it. - def part(params) - params = {:content_type => params} if String === params - if custom_headers = params.delete(:headers) - ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' << - 'Please just pass in custom headers directly.', caller[0,10]) - params.merge!(custom_headers) - end - part = Mail::Part.new(params) - yield part if block_given? - @parts << part - end - - # Add an attachment to a multipart message. This is simply a part with the - # content-disposition set to "attachment". - def attachment(params, &block) - params = { :content_type => params } if String === params - params = { :content_disposition => "attachment", - :content_transfer_encoding => "base64" }.merge(params) - if params[:body] - ActiveSupport::Deprecation.warn('attachment :body => "string" is deprecated. To set the body of an attachment ' << - 'please use :data instead, like attachment :data => "string".', caller[0,10]) - params[:data] = params.delete(:body) - end - part(params, &block) - end - class << self attr_writer :mailer_name @@ -475,21 +433,47 @@ module ActionMailer #:nodoc: superclass_delegating_reader :delivery_method self.delivery_method = :smtp + # Add a part to a multipart message, with the given content-type. The + # part itself is yielded to the block so that other properties (charset, + # body, headers, etc.) can be set on it. + def part(params) + params = {:content_type => params} if String === params + if custom_headers = params.delete(:headers) + ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' << + 'Please just pass in custom headers directly.', caller[0,10]) + params.merge!(custom_headers) + end + part = Mail::Part.new(params) + yield part if block_given? + @parts << part + end + + # Add an attachment to a multipart message. This is simply a part with the + # content-disposition set to "attachment". + def attachment(params, &block) + super # Run deprecation hooks + + params = { :content_type => params } if String === params + params = { :content_disposition => "attachment", + :content_transfer_encoding => "base64" }.merge(params) + + part(params, &block) + end + # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer # will be initialized according to the named method. If not, the mailer will # remain uninitialized (useful when you only need to invoke the "receive" # method, for instance). - def initialize(method_name=nil, *parameters) #:nodoc: - @_response_body = nil + def initialize(method_name=nil, *args) super() - create!(method_name, *parameters) if method_name + process(method_name, *args) if method_name end - # Initialize the mailer via the given +method_name+. The body will be + # Process the mailer via the given +method_name+. The body will be # rendered and a new Mail object created. - def create!(method_name, *parameters) #:nodoc: + def process(method_name, *args) initialize_defaults(method_name) - __send__(method_name, *parameters) + super # Create e-mail parts create_parts @@ -498,8 +482,8 @@ module ActionMailer #:nodoc: @subject ||= I18n.t(:subject, :scope => [:actionmailer, mailer_name, method_name], :default => method_name.humanize) - # build the mail object itself - @mail = create_mail + # Build the mail object itself + create_mail end # Delivers a Mail object. By default, it delivers the cached mail @@ -513,7 +497,7 @@ module ActionMailer #:nodoc: logger.debug "\n#{mail.encoded}" end - ActiveSupport::Notifications.instrument(:deliver_mail, :mail => @mail) do + ActiveSupport::Notifications.instrument(:deliver_mail, :mail => mail) do begin self.delivery_method.perform_delivery(mail) if perform_deliveries rescue Exception => e # Net::SMTP errors or sendmail pipe errors @@ -529,15 +513,14 @@ module ActionMailer #:nodoc: # Set up the default values for the various instance variables of this # mailer. Subclasses may override this method to provide different # defaults. - def initialize_defaults(method_name) + def initialize_defaults(method_name) #:nodoc: @charset ||= @@default_charset.dup @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup @mime_version ||= @@default_mime_version.dup if @@default_mime_version - @mailer_name ||= self.class.mailer_name + @mailer_name ||= self.class.mailer_name.dup @template ||= method_name - @action_name = @template @parts ||= [] @headers ||= {} @@ -546,29 +529,18 @@ module ActionMailer #:nodoc: super # Run deprecation hooks end - def create_parts + def create_parts #:nodoc: super # Run deprecation hooks if String === response_body - @parts.unshift Mail::Part.new( - :content_type => ["text", "plain", {:charset => charset}], - :content_disposition => "inline", - :body => response_body - ) + @parts.unshift create_inline_part(response_body) else - self.class.template_root.find_all(@template, {}, mailer_name).each do |template| - ct = template.mime_type ? template.mime_type.to_s : "text/plain" - main_type, sub_type = ct.split("/") - @parts << Mail::Part.new( - :content_type => [main_type, sub_type, {:charset => charset}], - :content_disposition => "inline", - :body => render_to_body(:_template => template) - ) + self.class.template_root.find_all(@template, {}, @mailer_name).each do |template| + @parts << create_inline_part(render_to_body(:_template => template), template.mime_type) end if @parts.size > 1 @content_type = "multipart/alternative" if @content_type !~ /^multipart/ - @parts = sort_parts(@parts, @implicit_parts_order) end # If this is a multipart e-mail add the mime_version if it is not @@ -577,36 +549,18 @@ module ActionMailer #:nodoc: end end - def sort_parts(parts, order = []) - order = order.collect { |s| s.downcase } - - parts = parts.sort do |a, b| - a_ct = a.content_type.string.downcase - b_ct = b.content_type.string.downcase - - a_in = order.include? a_ct - b_in = order.include? b_ct - - s = case - when a_in && b_in - order.index(a_ct) <=> order.index(b_ct) - when a_in - -1 - when b_in - 1 - else - a_ct <=> b_ct - end + def create_inline_part(body, mime_type=nil) #:nodoc: + ct = mime_type || "text/plain" + main_type, sub_type = split_content_type(ct.to_s) - # reverse the ordering because parts that come last are displayed - # first in mail clients - (s * -1) - end - - parts + Mail::Part.new( + :content_type => [main_type, sub_type, {:charset => charset}], + :content_disposition => "inline", + :body => body + ) end - def create_mail + def create_mail #:nodoc: m = Mail.new m.subject, = quote_any_if_necessary(charset, subject) @@ -620,13 +574,9 @@ module ActionMailer #:nodoc: headers.each { |k, v| m[k] = v } real_content_type, ctype_attrs = parse_content_type - - if @parts.empty? - main_type, sub_type = split_content_type(real_content_type) - m.content_type([main_type, sub_type, ctype_attrs]) - m.body = body - elsif @parts.size == 1 && @parts.first.parts.empty? - main_type, sub_type = split_content_type(real_content_type) + main_type, sub_type = split_content_type(real_content_type) + + if @parts.size == 1 && @parts.first.parts.empty? m.content_type([main_type, sub_type, ctype_attrs]) m.body = @parts.first.body.encoded else @@ -634,31 +584,33 @@ module ActionMailer #:nodoc: m.add_part(p) end + m.body.set_sort_order(@implicit_parts_order) + m.body.sort_parts! + if real_content_type =~ /multipart/ ctype_attrs.delete "charset" - main_type, sub_type = split_content_type(real_content_type) m.content_type([main_type, sub_type, ctype_attrs]) end end + m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii? + @mail = m end - def split_content_type(ct) + def split_content_type(ct) #:nodoc: ct.to_s.split("/") end - def parse_content_type(defaults=nil) - if content_type.blank? - return defaults ? - [ defaults.content_type, { 'charset' => defaults.charset } ] : - [ nil, {} ] - end - ctype, *attrs = content_type.split(/;\s*/) - attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h } - [ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)] + def parse_content_type(defaults=nil) #:nodoc: + if @content_type.blank? + [ nil, {} ] + else + ctype, *attrs = @content_type.split(/;\s*/) + attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h } + [ctype, {"charset" => @charset}.merge(attrs)] + end end - end end diff --git a/actionmailer/lib/action_mailer/delivery_method.rb b/actionmailer/lib/action_mailer/delivery_method.rb index 29a51afdc3..4f7d3afc3c 100644 --- a/actionmailer/lib/action_mailer/delivery_method.rb +++ b/actionmailer/lib/action_mailer/delivery_method.rb @@ -1,7 +1,7 @@ -require "active_support/core_ext/class" +require 'active_support/core_ext/class' + module ActionMailer module DeliveryMethod - autoload :File, 'action_mailer/delivery_method/file' autoload :Sendmail, 'action_mailer/delivery_method/sendmail' autoload :Smtp, 'action_mailer/delivery_method/smtp' @@ -52,6 +52,5 @@ module ActionMailer superclass_delegating_accessor :settings self.settings = {} end - end end diff --git a/actionmailer/lib/action_mailer/delivery_method/file.rb b/actionmailer/lib/action_mailer/delivery_method/file.rb index 99ef5bc76d..571e32df49 100644 --- a/actionmailer/lib/action_mailer/delivery_method/file.rb +++ b/actionmailer/lib/action_mailer/delivery_method/file.rb @@ -6,7 +6,7 @@ module ActionMailer # A delivery method implementation which writes all mails to a file. class File < Method self.settings = { - :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + :location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" } def perform_delivery(mail) diff --git a/actionmailer/lib/action_mailer/delivery_method/smtp.rb b/actionmailer/lib/action_mailer/delivery_method/smtp.rb index 9622dbb93a..af30c498b5 100644 --- a/actionmailer/lib/action_mailer/delivery_method/smtp.rb +++ b/actionmailer/lib/action_mailer/delivery_method/smtp.rb @@ -1,8 +1,9 @@ +require 'net/smtp' + module ActionMailer module DeliveryMethod # A delivery method implementation which sends via smtp. class Smtp < Method - self.settings = { :address => "localhost", :port => 25, @@ -25,6 +26,5 @@ module ActionMailer end end end - end end diff --git a/actionmailer/lib/action_mailer/deprecated_body.rb b/actionmailer/lib/action_mailer/deprecated_body.rb index 50ff262432..20b0989a85 100644 --- a/actionmailer/lib/action_mailer/deprecated_body.rb +++ b/actionmailer/lib/action_mailer/deprecated_body.rb @@ -15,6 +15,14 @@ module ActionMailer @body ||= {} end + def attachment(params, &block) + if params[:body] + ActiveSupport::Deprecation.warn('attachment :body => "string" is deprecated. To set the body of an attachment ' << + 'please use :data instead, like attachment :data => "string".', caller[0,10]) + params[:data] = params.delete(:body) + end + end + def create_parts if String === @body ActiveSupport::Deprecation.warn('body is deprecated. To set the body with a text ' << diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb index 351b966abe..701dc34431 100644 --- a/actionmailer/lib/action_mailer/mail_helper.rb +++ b/actionmailer/lib/action_mailer/mail_helper.rb @@ -1,17 +1,24 @@ -module MailHelper - # Uses Text::Format to take the text and format it, indented two spaces for - # each line, and wrapped at 72 columns. - def block_format(text) - formatted = text.split(/\n\r\n/).collect { |paragraph| - Text::Format.new( - :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph - ).format - }.join("\n") +module ActionMailer + module MailHelper + # Uses Text::Format to take the text and format it, indented two spaces for + # each line, and wrapped at 72 columns. + def block_format(text) + formatted = text.split(/\n\r\n/).collect { |paragraph| + Text::Format.new( + :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph + ).format + }.join("\n") - # Make list points stand on their own line - formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" } - formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" } + # Make list points stand on their own line + formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" } + formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" } + + formatted + end - formatted + # Access the mailer instance. + def mailer #:nodoc: + @controller + end end end diff --git a/actionmailer/lib/action_mailer/rails.rb b/actionmailer/lib/action_mailer/rails.rb new file mode 100644 index 0000000000..a3573cdea7 --- /dev/null +++ b/actionmailer/lib/action_mailer/rails.rb @@ -0,0 +1,24 @@ +require "action_mailer" + +module ActionMailer + class Plugin < Rails::Plugin + plugin_name :action_mailer + + initializer "action_mailer.set_configs" do |app| + app.config.action_mailer.each do |k,v| + ActionMailer::Base.send "#{k}=", v + end + end + + # TODO: ActionController::Base.logger should delegate to its own config.logger + initializer "action_mailer.logger" do + ActionMailer::Base.logger ||= Rails.logger + end + + initializer "action_mailer.view_paths" do |app| + # TODO: this should be combined with the logic for default config.action_mailer.view_paths + view_path = ActionView::PathSet.type_cast(app.config.view_path, app.config.cache_classes) + ActionMailer::Base.template_root = view_path if ActionMailer::Base.view_paths.blank? + end + end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index 49f6d680a2..445abd0b89 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -1,4 +1,5 @@ require 'active_support/test_case' +require 'mail' module ActionMailer class NonInferrableMailerError < ::StandardError diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index e84b3b0d23..af6f1bc92e 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,12 +1,9 @@ -root = File.expand_path('../../..', __FILE__) begin - require "#{root}/vendor/gems/environment" + require File.expand_path('../../../vendor/gems/environment', __FILE__) rescue LoadError - $:.unshift("#{root}/activesupport/lib") - $:.unshift("#{root}/actionpack/lib") end -lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +lib = File.expand_path('../../lib', __FILE__) $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'rubygems' diff --git a/actionmailer/test/adv_attr_test.rb b/actionmailer/test/adv_attr_test.rb index fd909a5627..f22d733bc5 100644 --- a/actionmailer/test/adv_attr_test.rb +++ b/actionmailer/test/adv_attr_test.rb @@ -1,18 +1,36 @@ require 'abstract_unit' require 'action_mailer/adv_attr_accessor' -class AdvAttrTest < Test::Unit::TestCase +class AdvAttrTest < ActiveSupport::TestCase class Person - include ActionMailer::AdvAttrAccessor + cattr_reader :protected_instance_variables + @@protected_instance_variables = [] + + extend ActionMailer::AdvAttrAccessor adv_attr_accessor :name end + def setup + @person = Person.new + end + def test_adv_attr - bob = Person.new - assert_nil bob.name - bob.name 'Bob' - assert_equal 'Bob', bob.name + assert_nil @person.name + @person.name 'Bob' + assert_equal 'Bob', @person.name + end + + def test_adv_attr_writer + assert_nil @person.name + @person.name = 'Bob' + assert_equal 'Bob', @person.name + end + + def test_raise_an_error_with_multiple_args + assert_raise(ArgumentError) { @person.name('x', 'y') } + end - assert_raise(ArgumentError) {bob.name 'x', 'y'} + def test_ivar_is_added_to_protected_instnace_variables + assert Person.protected_instance_variables.include?('@name') end end diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb index 0b74fe8c24..f3383e5608 100644 --- a/actionmailer/test/asset_host_test.rb +++ b/actionmailer/test/asset_host_test.rb @@ -24,7 +24,7 @@ class AssetHostTest < Test::Unit::TestCase def test_asset_host_as_string ActionController::Base.asset_host = "http://www.example.com" mail = AssetHostMailer.deliver_email_with_asset(@recipient) - assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.decoded.strip + 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 @@ -36,7 +36,7 @@ class AssetHostTest < Test::Unit::TestCase end } mail = AssetHostMailer.deliver_email_with_asset(@recipient) - assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.decoded.strip + assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.to_s.strip end def test_asset_host_as_two_arguement_proc @@ -49,6 +49,6 @@ class AssetHostTest < Test::Unit::TestCase } mail = nil assert_nothing_raised { mail = AssetHostMailer.deliver_email_with_asset(@recipient) } - assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.decoded.strip + assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.to_s.strip end end
\ No newline at end of file diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index d04215171d..2d3565d159 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -56,7 +56,7 @@ end class MailerHelperTest < Test::Unit::TestCase def new_mail( charset="utf-8" ) mail = Mail.new - mail.content_type(["text", "plain", { "charset" => charset }]) + mail.set_content_type "text", "plain", { "charset" => charset } if charset mail end diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 4775545039..84f13a6d3c 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -65,7 +65,7 @@ class LayoutMailerTest < Test::Unit::TestCase def test_should_pickup_default_layout mail = AutoLayoutMailer.create_hello(@recipient) - assert_equal "Hello from layout Inside", mail.body.decoded.strip + assert_equal "Hello from layout Inside", mail.body.to_s.strip end def test_should_pickup_multipart_layout @@ -81,7 +81,7 @@ class LayoutMailerTest < Test::Unit::TestCase # CHANGED: body returns an object # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body - assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.decoded + assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s # CHANGED: content_type returns an object # assert_equal 'text/html', mail.parts.last.content_type @@ -89,7 +89,7 @@ class LayoutMailerTest < Test::Unit::TestCase # CHANGED: body returns an object # assert_equal "Hello from layout text/html multipart", mail.parts.last.body - assert_equal "Hello from layout text/html multipart", mail.parts.last.body.decoded + assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_pickup_multipartmixed_layout @@ -104,14 +104,14 @@ class LayoutMailerTest < Test::Unit::TestCase assert_equal 'text/plain', mail.parts.first.content_type.string # CHANGED: body returns an object # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body - assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.decoded + assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s # CHANGED: content_type returns an object # assert_equal 'text/html', mail.parts.last.content_type assert_equal 'text/html', mail.parts.last.content_type.string # CHANGED: body returns an object # assert_equal "Hello from layout text/html multipart", mail.parts.last.body - assert_equal "Hello from layout text/html multipart", mail.parts.last.body.decoded + assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_fix_multipart_layout @@ -120,30 +120,30 @@ class LayoutMailerTest < Test::Unit::TestCase assert_equal 2, mail.parts.size assert_equal 'text/plain', mail.parts.first.content_type.string - assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.decoded + assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s assert_equal 'text/html', mail.parts.last.content_type.string - assert_equal "Hello from layout text/html multipart", mail.parts.last.body.decoded + assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_pickup_layout_given_to_render mail = AutoLayoutMailer.create_spam(@recipient) - assert_equal "Spammer layout Hello, Earth", mail.body.decoded.strip + assert_equal "Spammer layout Hello, Earth", mail.body.to_s.strip end def test_should_respect_layout_false mail = AutoLayoutMailer.create_nolayout(@recipient) - assert_equal "Hello, Earth", mail.body.decoded.strip + assert_equal "Hello, Earth", mail.body.to_s.strip end def test_explicit_class_layout mail = ExplicitLayoutMailer.create_signup(@recipient) - assert_equal "Spammer layout We do not spam", mail.body.decoded.strip + assert_equal "Spammer layout We do not spam", mail.body.to_s.strip end def test_explicit_layout_exceptions mail = ExplicitLayoutMailer.create_logout(@recipient) - assert_equal "You logged out", mail.body.decoded.strip + assert_equal "You logged out", mail.body.to_s.strip end end diff --git a/actionmailer/test/mail_render_test.rb b/actionmailer/test/mail_render_test.rb index 3c97ad2a03..09ce5e4854 100644 --- a/actionmailer/test/mail_render_test.rb +++ b/actionmailer/test/mail_render_test.rb @@ -40,13 +40,22 @@ class RenderMailer < ActionMailer::Base from "tester@example.com" end - def included_old_subtemplate(recipient) + def mailer_accessor(recipient) recipients recipient - subject "Including another template in the one being rendered" + subject "Mailer Accessor" from "tester@example.com" - @world = "Earth" - render :inline => "Hello, <%= render \"subtemplate\" %>" + render :inline => "Look, <%= mailer.subject %>!" + end + + def no_instance_variable(recipient) + recipients recipient + subject "No Instance Variable" + from "tester@example.com" + + silence_warnings do + render :inline => "Look, subject.nil? is <%= @subject.nil? %>!" + end end def initialize_defaults(method_name) @@ -71,6 +80,8 @@ class SecondMailer < ActionMailer::Base end end +# CHANGED: Those tests were changed because body returns an object now +# Instead of mail.body.strip, we should mail.body.to_s.strip class RenderHelperTest < Test::Unit::TestCase def setup set_delivery_method :test @@ -86,37 +97,37 @@ class RenderHelperTest < Test::Unit::TestCase def test_implicit_body mail = RenderMailer.create_implicit_body(@recipient) - # CHANGED: body returns an object now - # assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip - assert_equal "Hello there, \n\nMr. test@localhost", mail.body.decoded.strip + assert_equal "Hello there, \n\nMr. test@localhost", mail.body.to_s.strip end def test_inline_template mail = RenderMailer.create_inline_template(@recipient) - # CHANGED: body returns an object now - # assert_equal "Hello, Earth", mail.body.strip - assert_equal "Hello, Earth", mail.body.decoded.strip + assert_equal "Hello, Earth", mail.body.to_s.strip end def test_file_template mail = RenderMailer.create_file_template(@recipient) - # CHANGED: body returns an object now - # assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip - assert_equal "Hello there, \n\nMr. test@localhost", mail.body.decoded.strip + assert_equal "Hello there, \n\nMr. test@localhost", mail.body.to_s.strip end def test_rxml_template mail = RenderMailer.deliver_rxml_template(@recipient) - # CHANGED: body returns an object now - # assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.strip - assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.decoded.strip + assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.to_s.strip end def test_included_subtemplate mail = RenderMailer.deliver_included_subtemplate(@recipient) - # CHANGED: body returns an object now - # assert_equal "Hey Ho, let's go!", mail.body.strip - assert_equal "Hey Ho, let's go!", mail.body.decoded.strip + assert_equal "Hey Ho, let's go!", mail.body.to_s.strip + end + + def test_mailer_accessor + mail = RenderMailer.deliver_mailer_accessor(@recipient) + assert_equal "Look, Mailer Accessor!", mail.body.to_s.strip + end + + def test_no_instance_variable + mail = RenderMailer.deliver_no_instance_variable(@recipient) + assert_equal "Look, subject.nil? is true!", mail.body.to_s.strip end end @@ -135,12 +146,12 @@ class FirstSecondHelperTest < Test::Unit::TestCase def test_ordering mail = FirstMailer.create_share(@recipient) - assert_equal "first mail", mail.body.decoded.strip + assert_equal "first mail", mail.body.to_s.strip mail = SecondMailer.create_share(@recipient) - assert_equal "second mail", mail.body.decoded.strip + assert_equal "second mail", mail.body.to_s.strip mail = FirstMailer.create_share(@recipient) - assert_equal "first mail", mail.body.decoded.strip + assert_equal "first mail", mail.body.to_s.strip mail = SecondMailer.create_share(@recipient) - assert_equal "second mail", mail.body.decoded.strip + assert_equal "second mail", mail.body.to_s.strip end end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 7a47a654cd..152900259d 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -31,6 +31,18 @@ class TestMailer < ActionMailer::Base render :text => "Goodbye, Mr. #{recipient}" end + def from_with_name + from "System <system@loudthinking.com>" + recipients "root@loudthinking.com" + render :text => "Nothing to see here." + end + + def from_without_name + from "system@loudthinking.com" + recipients "root@loudthinking.com" + render :text => "Nothing to see here." + end + def cc_bcc(recipient) recipients recipient subject "testing bcc/cc" @@ -95,7 +107,7 @@ class TestMailer < ActionMailer::Base cc "Foo áëô îü <extended@example.net>" bcc "Foo áëô îü <extended@example.net>" charset "utf-8" - + render :text => "åœö blah" end @@ -227,7 +239,7 @@ class TestMailer < ActionMailer::Base from "test@example.com" content_type "multipart/mixed" - part :content_type => "multipart/alternative", :content_disposition => "inline", :headers => { "foo" => "bar" } do |p| + part :content_type => "multipart/alternative", :content_disposition => "inline", "foo" => "bar" do |p| p.part :content_type => "text/plain", :body => "test text\nline #2" p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2" end @@ -252,7 +264,7 @@ class TestMailer < ActionMailer::Base from "test@example.com" content_type "multipart/related" part :content_type => "text/html", :body => 'yo' - attachment :content_type => "image/jpeg", :filename => File.join(File.dirname(__FILE__), "fixtures", "attachments", "test.jpg"), :data => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' } + attachment :content_type => "image/jpeg", :filename => File.join(File.dirname(__FILE__), "fixtures", "attachments", "test.jpg"), :data => "i am not a real picture", 'Content-ID' => '<test@test.com>' end def unnamed_attachment(recipient) @@ -289,6 +301,7 @@ class TestMailer < ActionMailer::Base render :text => "testing" end + # This tests body calls accepeting a hash, which is deprecated. def body_ivar(recipient) recipients recipient subject "Body as a local variable" @@ -351,7 +364,7 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal "multipart/mixed", created.content_type.string assert_equal "multipart/alternative", created.parts[0].content_type.string - assert_equal "bar", created.parts[0].header['foo'].decoded + assert_equal "bar", created.parts[0].header['foo'].to_s assert_nil created.parts[0].charset assert_equal "text/plain", created.parts[0].parts[0].content_type.string assert_equal "text/html", created.parts[0].parts[1].content_type.string @@ -370,9 +383,9 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal "multipart/mixed", created.content_type.string assert_equal "multipart/alternative", created.parts.first.content_type.string assert_equal "text/plain", created.parts.first.parts.first.content_type.string - assert_equal "Nothing to see here.", created.parts.first.parts.first.body.decoded + assert_equal "Nothing to see here.", created.parts.first.parts.first.body.to_s assert_equal "text/html", created.parts.first.parts.second.content_type.string - assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.decoded + assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.to_s end def test_attachment_with_custom_header @@ -413,11 +426,11 @@ class ActionMailerTest < Test::Unit::TestCase def test_subject_with_i18n assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } - assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject.decoded + assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject.to_s I18n.backend.store_translations('en', :actionmailer => {:test_mailer => {:subject_with_i18n => {:subject => "New Subject!"}}}) assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } - assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject.decoded + assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject.to_s end def test_custom_template @@ -514,6 +527,28 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal expected.encoded, delivered.encoded end + def test_from_without_name_for_smtp + ActionMailer::Base.delivery_method = :smtp + TestMailer.deliver_from_without_name + + mail = MockSMTP.deliveries.first + assert_not_nil mail + mail, from, to = mail + + assert_equal 'system@loudthinking.com', from.to_s + end + + def test_from_with_name_for_smtp + ActionMailer::Base.delivery_method = :smtp + TestMailer.deliver_from_with_name + + mail = MockSMTP.deliveries.first + assert_not_nil mail + mail, from, to = mail + + assert_equal 'system@loudthinking.com', from.addresses.first + end + def test_reply_to expected = new_mail @@ -696,7 +731,7 @@ Content-Type: text/plain; charset=iso-8859-1 The body EOF mail = Mail.new(msg) - assert_equal "testing testing \326\244", mail.subject.decoded + assert_equal "testing testing \326\244", mail.subject.to_s assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail.subject.encoded end @@ -709,7 +744,7 @@ Content-Type: text/plain; charset=iso-8859-1 The body EOF mail = Mail.new(msg) - assert_equal "this == working?", mail.subject.decoded + assert_equal "this == working?", mail.subject.to_s assert_equal "Subject: this == working?\r\n", mail.subject.encoded end @@ -723,7 +758,7 @@ Content-Transfer-Encoding: 7bit The=3Dbody EOF mail = Mail.new(msg) - assert_equal "The=3Dbody", mail.body.decoded.strip + assert_equal "The=3Dbody", mail.body.to_s.strip assert_equal "The=3Dbody", mail.body.encoded.strip end @@ -737,7 +772,7 @@ Content-Transfer-Encoding: quoted-printable The=3Dbody EOF mail = Mail.new(msg) - assert_equal "The=body", mail.body.decoded.strip + assert_equal "The=body", mail.body.to_s.strip assert_equal "The=3Dbody", mail.body.encoded.strip end @@ -751,7 +786,7 @@ Content-Transfer-Encoding: base64 VGhlIGJvZHk= EOF mail = Mail.new(msg) - assert_equal "The body", mail.body.decoded.strip + assert_equal "The body", mail.body.to_s.strip assert_equal "VGhlIGJvZHk=", mail.body.encoded.strip end @@ -825,7 +860,7 @@ EOF def test_receive_decodes_base64_encoded_mail fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email") TestMailer.receive(fixture) - assert_match(/Jamis/, TestMailer.received_body.decoded) + assert_match(/Jamis/, TestMailer.received_body.to_s) end def test_receive_attachments @@ -895,7 +930,6 @@ EOF mail = TestMailer.create_explicitly_multipart_example(@recipient) assert_equal 3, mail.parts.length assert_equal 'multipart/mixed', mail.content_type.string - assert_equal "text/plain", mail.parts[0].content_type.string assert_equal "text/html", mail.parts[1].content_type.string @@ -903,7 +937,6 @@ EOF assert_equal "image/jpeg", mail.parts[2].content_type.string assert_equal "attachment", mail.parts[2].content_disposition.disposition_type - assert_equal "foo.jpg", mail.parts[2].content_disposition.filename assert_equal "foo.jpg", mail.parts[2].content_type.filename assert_nil mail.parts[2].charset @@ -926,13 +959,13 @@ EOF mail = TestMailer.create_implicitly_multipart_example(@recipient) assert_equal 3, mail.parts.length - assert_equal "1.0", mail.mime_version.decoded + assert_equal "1.0", mail.mime_version.to_s assert_equal "multipart/alternative", mail.content_type.string - assert_equal "application/x-yaml", mail.parts[0].content_type.string + assert_equal "text/plain", mail.parts[0].content_type.string assert_equal "utf-8", mail.parts[0].charset - assert_equal "text/plain", mail.parts[1].content_type.string + assert_equal "text/html", mail.parts[1].content_type.string assert_equal "utf-8", mail.parts[1].charset - assert_equal "text/html", mail.parts[2].content_type.string + assert_equal "application/x-yaml", mail.parts[2].content_type.string assert_equal "utf-8", mail.parts[2].charset end @@ -941,9 +974,9 @@ EOF mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["application/x-yaml", "text/plain"]) assert_equal 3, mail.parts.length - assert_equal "text/html", mail.parts[0].content_type.string + assert_equal "application/x-yaml", mail.parts[0].content_type.string assert_equal "text/plain", mail.parts[1].content_type.string - assert_equal "application/x-yaml", mail.parts[2].content_type.string + assert_equal "text/html", mail.parts[2].content_type.string end def test_implicitly_multipart_messages_with_charset @@ -963,19 +996,19 @@ EOF def test_html_mail_with_underscores mail = TestMailer.create_html_mail_with_underscores(@recipient) - assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body.decoded + assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body.to_s end def test_various_newlines mail = TestMailer.create_various_newlines(@recipient) assert_equal("line #1\nline #2\nline #3\nline #4\n\n" + - "line #5\n\nline#6\n\nline #7", mail.body.decoded) + "line #5\n\nline#6\n\nline #7", mail.body.to_s) end def test_various_newlines_multipart mail = TestMailer.create_various_newlines_multipart(@recipient) - assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body.decoded - assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body.decoded + assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body.to_s + assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body.to_s assert_equal "line #1\r\nline #2\r\nline #3\r\nline #4\r\n\r\n", mail.parts[0].body.encoded assert_equal "<p>line #1</p>\r\n<p>line #2</p>\r\n<p>line #3</p>\r\n<p>line #4</p>\r\n\r\n", mail.parts[1].body.encoded end @@ -1008,7 +1041,7 @@ EOF mail = Mail.new(fixture) assert_equal(2, mail.parts.length) assert_equal(4, mail.parts.first.parts.length) - assert_equal("This is the first part.", mail.parts.first.parts.first.body.decoded) + assert_equal("This is the first part.", mail.parts.first.parts.first.body.to_s) assert_equal("test.rb", mail.parts.first.parts.second.filename) assert_equal("flowed", mail.parts.first.parts.fourth.content_type.parameters[:format]) assert_equal('smime.p7s', mail.parts.second.filename) @@ -1074,7 +1107,7 @@ EOF def test_return_path_with_create mail = TestMailer.create_return_path - assert_equal "another@somewhere.test", mail['return-path'].decoded.to_s + assert_equal "another@somewhere.test", mail['return-path'].to_s end def test_return_path_with_deliver @@ -1085,8 +1118,9 @@ EOF end def test_body_is_stored_as_an_ivar - mail = TestMailer.create_body_ivar(@recipient) - assert_equal "body: foo\nbar: baz", mail.body.decoded + mail = nil + ActiveSupport::Deprecation.silence { mail = TestMailer.create_body_ivar(@recipient) } + assert_equal "body: foo\nbar: baz", mail.body.to_s end def test_starttls_is_enabled_if_supported @@ -1215,6 +1249,6 @@ class RespondToTest < Test::Unit::TestCase RespondToMailer.not_a_method end - assert_match /undefined method.*not_a_method/, error.message + assert_match(/undefined method.*not_a_method/, error.message) end end diff --git a/actionmailer/test/tmail_test.rb b/actionmailer/test/mail_test.rb index 5d895f3790..ea6f25d157 100644 --- a/actionmailer/test/tmail_test.rb +++ b/actionmailer/test/mail_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class TMailMailTest < Test::Unit::TestCase +class MailTest < Test::Unit::TestCase def test_body m = Mail.new expected = 'something_with_underscores' @@ -8,8 +8,8 @@ class TMailMailTest < Test::Unit::TestCase quoted_body = [expected].pack('*M') m.body = quoted_body assert_equal "something_with_underscores=\r\n", m.body.encoded - # CHANGED: body returns object, not string, Changed m.body to m.body.decoded - assert_equal expected, m.body.decoded + # CHANGED: body returns object, not string, Changed m.body to m.body.to_s + assert_equal expected, m.body.to_s end def test_nested_attachments_are_recognized_correctly diff --git a/actionmailer/test/quoting_test.rb b/actionmailer/test/quoting_test.rb index 32f34f6028..b16d160805 100644 --- a/actionmailer/test/quoting_test.rb +++ b/actionmailer/test/quoting_test.rb @@ -71,7 +71,7 @@ class QuotingTest < Test::Unit::TestCase mail = Mail.new(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a")) # CHANGED: subject returns an object now # assert_match %r{Elapsed time}, mail.body - assert_match %r{Elapsed time}, mail.body.decoded + assert_match %r{Elapsed time}, mail.body.to_s end def test_email_with_partially_quoted_subject diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 8e4254411c..86d22da9bf 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -92,7 +92,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end end - assert_match /2 .* but 1/, error.message + assert_match(/2 .* but 1/, error.message) end def test_assert_emails_too_many_sent @@ -103,7 +103,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end end - assert_match /1 .* but 2/, error.message + assert_match(/1 .* but 2/, error.message) end def test_assert_no_emails_failure @@ -113,7 +113,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end end - assert_match /0 .* but 1/, error.message + assert_match(/0 .* but 1/, error.message) end end diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 427833b36c..12bf609dce 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -1,7 +1,9 @@ require 'abstract_unit' -class TestMailer < ActionMailer::Base +class WelcomeController < ActionController::Base +end +class TestMailer < ActionMailer::Base default_url_options[:host] = 'www.basecamphq.com' def signed_up_with_url(recipient) @@ -10,8 +12,8 @@ class TestMailer < ActionMailer::Base @from = "system@loudthinking.com" @sent_on = Time.local(2004, 12, 12) - @body["recipient"] = recipient - @body["welcome_url"] = url_for :host => "example.com", :controller => "welcome", :action => "greeting" + @recipient = recipient + @welcome_url = url_for :host => "example.com", :controller => "welcome", :action => "greeting" end class <<self @@ -52,27 +54,31 @@ class ActionMailerUrlTest < Test::Unit::TestCase end def test_signed_up_with_url - ActionController::Routing.use_controllers! ['welcome'] do - ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' - map.welcome 'welcome', :controller=>"foo", :action=>"bar" - end - - expected = new_mail - expected.to = @recipient - expected.subject = "[Signed up] Welcome #{@recipient}" - expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />" - expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) - - created = nil - assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) } - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) } - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded + ActionController::Routing::Routes.draw do |map| + map.connect ':controller/:action/:id' + map.welcome 'welcome', :controller=>"foo", :action=>"bar" end + + expected = new_mail + expected.to = @recipient + expected.subject = "[Signed up] Welcome #{@recipient}" + expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />" + expected.from = "system@loudthinking.com" + expected.date = Time.local(2004, 12, 12) + + created = nil + assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) } + assert_not_nil created + + expected.message_id = '<123@456>' + created.message_id = '<123@456>' + assert_equal expected.encoded, created.encoded + + assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) } + assert_not_nil ActionMailer::Base.deliveries.first + delivered = ActionMailer::Base.deliveries.first + + delivered.message_id = '<123@456>' + assert_equal expected.encoded, delivered.encoded end end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 30f3f31563..782b4229fb 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,19 @@ *Edge* +* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH] + +* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples: + + flash[:notice] = 'Post was created' + redirect_to(@post) + + ...becomes: + + redirect_to(@post, :notice => 'Post was created') + +* Added ActionController::Base#notice/= and ActionController::Base#alert/= as a convenience accessors in both the controller and the view for flash[:notice]/= and flash[:alert]/= [DHH] + + * Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom] * Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs] diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 99bdcc95fa..863daa4b44 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -31,7 +31,6 @@ Rake::TestTask.new(:test_action_pack) do |t| # this will not happen automatically and the tests (as a whole) will error t.test_files = Dir.glob('test/{abstract,controller,dispatch,template}/**/*_test.rb').sort - t.verbose = true # t.warning = true end @@ -45,7 +44,6 @@ desc 'ActiveRecord Integration Tests' Rake::TestTask.new(:test_active_record_integration) do |t| t.libs << 'test' t.test_files = Dir.glob("test/activerecord/*_test.rb") - t.verbose = true end # Genereate the RDoc documentation diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 78286f6747..dc91fba64d 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -16,9 +16,9 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '= 3.0.pre') s.add_dependency('activemodel', '= 3.0.pre') - s.add_dependency('rack', '~> 1.0.1') + s.add_dependency('rack', '~> 1.1.0') s.add_dependency('rack-test', '~> 0.5.0') - s.add_dependency('rack-mount', '~> 0.2') + s.add_dependency('rack-mount', '~> 0.4.0') s.add_dependency('erubis', '~> 2.6.5') s.require_path = 'lib' diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 1a6c4278c9..237ab577ba 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -1,16 +1,18 @@ -require "active_support/core_ext/module/attr_internal" -require "active_support/core_ext/module/delegation" +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + +require 'active_support/ruby/shim' +require 'active_support/core_ext/module/attr_internal' +require 'active_support/core_ext/module/delegation' module AbstractController - autoload :Base, "abstract_controller/base" - autoload :Callbacks, "abstract_controller/callbacks" - autoload :Helpers, "abstract_controller/helpers" - autoload :Layouts, "abstract_controller/layouts" - autoload :LocalizedCache, "abstract_controller/localized_cache" - autoload :Logger, "abstract_controller/logger" - autoload :RenderingController, "abstract_controller/rendering_controller" - # === Exceptions - autoload :ActionNotFound, "abstract_controller/exceptions" - autoload :DoubleRenderError, "abstract_controller/exceptions" - autoload :Error, "abstract_controller/exceptions" + extend ActiveSupport::Autoload + + autoload :Base + autoload :Callbacks + autoload :Helpers + autoload :Layouts + autoload :LocalizedCache + autoload :Logger + autoload :Rendering end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index f5b1c9e4d1..efea81aa71 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,8 +1,11 @@ module AbstractController + class Error < StandardError; end + class ActionNotFound < StandardError; end class Base attr_internal :response_body attr_internal :action_name + attr_internal :formats class << self attr_reader :abstract @@ -74,21 +77,23 @@ module AbstractController abstract! # Calls the action going through the entire action dispatch stack. - # + # # The actual method that is called is determined by calling # #method_for_action. If no method can handle the action, then an # ActionNotFound error is raised. # # ==== Returns # self - def process(action) + def process(action, *args) @_action_name = action_name = action.to_s unless action_name = method_for_action(action_name) raise ActionNotFound, "The action '#{action}' could not be found" end - process_action(action_name) + @_response_body = nil + + process_action(action_name, *args) end private @@ -108,8 +113,8 @@ module AbstractController # Call the action. Override this in a subclass to modify the # behavior around processing an action. This, and not #process, # is the intended way to override action dispatching. - def process_action(method_name) - send_action(method_name) + def process_action(method_name, *args) + send_action(method_name, *args) end # Actually call the method associated with the action. Override diff --git a/actionpack/lib/abstract_controller/exceptions.rb b/actionpack/lib/abstract_controller/exceptions.rb deleted file mode 100644 index b671516de1..0000000000 --- a/actionpack/lib/abstract_controller/exceptions.rb +++ /dev/null @@ -1,12 +0,0 @@ -module AbstractController - class Error < StandardError; end - class ActionNotFound < StandardError; end - - class DoubleRenderError < Error - DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end -end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index d3b492ad09..1d898d1a4c 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -4,7 +4,7 @@ module AbstractController module Helpers extend ActiveSupport::Concern - include RenderingController + include Rendering def self.next_serial @helper_serial ||= 0 diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index c71cef42b2..46760bba7c 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -2,7 +2,7 @@ module AbstractController module Layouts extend ActiveSupport::Concern - include RenderingController + include Rendering included do extlib_inheritable_accessor(:_layout_conditions) { Hash.new } diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index 27ba5be45f..e3bcd28da7 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -29,33 +29,5 @@ module AbstractController @str.send(*args, &block) end end - - # Override process_action in the AbstractController::Base - # to log details about the method. - def process_action(action) - result = ActiveSupport::Notifications.instrument(:process_action, - :controller => self, :action => action) do - super - end - - if logger - log = DelayedLog.new do - "\n\nProcessing #{self.class.name}\##{action_name} " \ - "to #{request.formats} (for #{request_origin}) " \ - "[#{request.method.to_s.upcase}]" - end - - logger.info(log) - end - - result - end - - private - # Returns the request origin with the IP and time. This needs to be cached, - # otherwise we would get different results for each time it calls. - def request_origin - @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}" - end end end diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering.rb index 7054b9cf26..64a8a5f241 100644 --- a/actionpack/lib/abstract_controller/rendering_controller.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,13 +1,21 @@ +require "abstract_controller/base" require "abstract_controller/logger" module AbstractController - module RenderingController + class DoubleRenderError < Error + DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Rendering extend ActiveSupport::Concern include AbstractController::Logger included do - attr_internal :formats extlib_inheritable_accessor :_view_paths self._view_paths ||= ActionView::PathSet.new end @@ -21,7 +29,7 @@ module AbstractController # 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 + # View.for_controller[controller] Create a new ActionView instance for a # controller # View#render_partial[options] # - responsible for setting options[:_template] @@ -71,7 +79,7 @@ module AbstractController # # :api: plugin def render_to_string(options = {}) - AbstractController::RenderingController.body_to_s(render_to_body(options)) + AbstractController::Rendering.body_to_s(render_to_body(options)) end # Renders the template from an object. @@ -115,10 +123,10 @@ module AbstractController # _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::TextTemplate.new(options[:text], format_for_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 #{options[:inline].inspect}", handler, {}) + 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] @@ -152,12 +160,12 @@ module AbstractController 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 + # 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 << path @@ -166,8 +174,8 @@ module AbstractController # 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 + # 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! @@ -186,9 +194,8 @@ module AbstractController # 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) + self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) end 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 03a40e4fce..26a85d4de8 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,66 +1,78 @@ +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +require 'active_support/ruby/shim' + module ActionController - autoload :Base, "action_controller/base" - autoload :Benchmarking, "action_controller/metal/benchmarking" - autoload :ConditionalGet, "action_controller/metal/conditional_get" - autoload :Configuration, "action_controller/metal/configuration" - autoload :Head, "action_controller/metal/head" - autoload :Helpers, "action_controller/metal/helpers" - autoload :HideActions, "action_controller/metal/hide_actions" - autoload :Layouts, "action_controller/metal/layouts" - autoload :Metal, "action_controller/metal" - autoload :Middleware, "action_controller/middleware" - autoload :RackConvenience, "action_controller/metal/rack_convenience" - autoload :Rails2Compatibility, "action_controller/metal/compatibility" - autoload :Redirector, "action_controller/metal/redirector" - autoload :RenderingController, "action_controller/metal/rendering_controller" - autoload :RenderOptions, "action_controller/metal/render_options" - autoload :Rescue, "action_controller/metal/rescuable" - autoload :Responder, "action_controller/metal/responder" - autoload :Session, "action_controller/metal/session" - autoload :Testing, "action_controller/metal/testing" - autoload :UrlFor, "action_controller/metal/url_for" + extend ActiveSupport::Autoload - autoload :Caching, 'action_controller/caching' - autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :Integration, 'action_controller/deprecated/integration_test' - autoload :IntegrationTest, 'action_controller/deprecated/integration_test' - autoload :MimeResponds, 'action_controller/metal/mime_responds' - autoload :PerformanceTest, 'action_controller/deprecated/performance_test' - autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' - autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Routing, 'action_controller/deprecated' - autoload :SessionManagement, 'action_controller/metal/session_management' - autoload :TestCase, 'action_controller/testing/test_case' - autoload :TestProcess, 'action_controller/testing/process' - autoload :UrlRewriter, 'action_controller/url_rewriter' - autoload :UrlWriter, 'action_controller/url_rewriter' + autoload :Base + autoload :Caching + autoload :PolymorphicRoutes + autoload :Translation + autoload :Metal + autoload :Middleware - autoload :Verification, 'action_controller/metal/verification' - autoload :Flash, 'action_controller/metal/flash' - autoload :RequestForgeryProtection, 'action_controller/metal/request_forgery_protection' - autoload :Streaming, 'action_controller/metal/streaming' - autoload :HttpAuthentication, 'action_controller/metal/http_authentication' - autoload :FilterParameterLogging, 'action_controller/metal/filter_parameter_logging' - autoload :Translation, 'action_controller/translation' - autoload :Cookies, 'action_controller/metal/cookies' + autoload_under "metal" do + autoload :Benchmarking + autoload :ConditionalGet + autoload :Configuration + autoload :Head + autoload :Helpers + autoload :HideActions + autoload :Layouts + autoload :Logger + autoload :MimeResponds + autoload :RackDelegation + autoload :Compatibility + autoload :Redirecting + autoload :Rendering + autoload :Renderers + autoload :Rescue + autoload :Responder + autoload :SessionManagement + autoload :UrlFor + autoload :Verification + autoload :Flash + autoload :RequestForgeryProtection + autoload :Streaming + autoload :HttpAuthentication + autoload :FilterParameterLogging + autoload :Cookies + end - autoload :ActionControllerError, 'action_controller/metal/exceptions' - autoload :RenderError, 'action_controller/metal/exceptions' - autoload :RoutingError, 'action_controller/metal/exceptions' - autoload :MethodNotAllowed, 'action_controller/metal/exceptions' - autoload :NotImplemented, 'action_controller/metal/exceptions' - autoload :UnknownController, 'action_controller/metal/exceptions' - autoload :MissingFile, 'action_controller/metal/exceptions' - autoload :RenderError, 'action_controller/metal/exceptions' - autoload :SessionOverflowError, 'action_controller/metal/exceptions' - autoload :UnknownHttpMethod, 'action_controller/metal/exceptions' -end + autoload :Dispatcher, 'action_controller/dispatch/dispatcher' + autoload :PerformanceTest, 'action_controller/deprecated/performance_test' + autoload :Routing, 'action_controller/deprecated' + autoload :Integration, 'action_controller/deprecated/integration_test' + autoload :IntegrationTest, 'action_controller/deprecated/integration_test' -autoload :HTML, 'action_controller/vendor/html-scanner' -autoload :AbstractController, 'abstract_controller' + eager_autoload do + autoload :RecordIdentifier + autoload :UrlRewriter + autoload :UrlWriter, 'action_controller/url_rewriter' + + # TODO: Don't autoload exceptions, setup explicit + # requires for files that need them + autoload_at "action_controller/metal/exceptions" do + autoload :ActionControllerError + autoload :RenderError + autoload :RoutingError + autoload :MethodNotAllowed + autoload :NotImplemented + autoload :UnknownController + autoload :MissingFile + autoload :RenderError + autoload :SessionOverflowError + autoload :UnknownHttpMethod + end + end +end +# All of these simply register additional autoloads +require 'abstract_controller' require 'action_dispatch' require 'action_view' +require 'action_controller/vendor/html-scanner' # Common ActiveSupport usage in ActionController require "active_support/concern" diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 4c026fe5f7..dbba69f637 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -8,26 +8,25 @@ module ActionController include ActionController::Helpers include ActionController::HideActions include ActionController::UrlFor - include ActionController::Redirector - include ActionController::RenderingController - include ActionController::RenderOptions::All + include ActionController::Redirecting + include ActionController::Rendering + include ActionController::Renderers::All include ActionController::Layouts include ActionController::ConditionalGet - include ActionController::RackConvenience + include ActionController::RackDelegation + include ActionController::Logger include ActionController::Benchmarking include ActionController::Configuration # Legacy modules include SessionManagement - include ActionDispatch::StatusCodes include ActionController::Caching include ActionController::MimeResponds # Rails 2.x compatibility - include ActionController::Rails2Compatibility + include ActionController::Compatibility include ActionController::Cookies - include ActionController::Session include ActionController::Flash include ActionController::Verification include ActionController::RequestForgeryProtection @@ -91,7 +90,7 @@ module ActionController end if options[:status] - options[:status] = interpret_status(options[:status]).to_i + options[:status] = Rack::Utils.status_code(options[:status]) end options[:update] = blk if block_given? @@ -107,62 +106,5 @@ module ActionController options = _normalize_options(action, options, &blk) super(options) end - - # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: - # - # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+. - # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection. - # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string. - # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt> - # - # Examples: - # redirect_to :action => "show", :id => 5 - # redirect_to post - # redirect_to "http://www.rubyonrails.org" - # redirect_to "/images/screenshot.jpg" - # redirect_to articles_url - # redirect_to :back - # - # The redirection happens as a "302 Moved" header unless otherwise specified. - # - # Examples: - # redirect_to post_url(@post), :status=>:found - # redirect_to :action=>'atom', :status=>:moved_permanently - # redirect_to post_url(@post), :status=>301 - # redirect_to :action=>'atom', :status=>302 - # - # When using <tt>redirect_to :back</tt>, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback - # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: - raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? - - status = if options.is_a?(Hash) && options.key?(:status) - interpret_status(options.delete(:status)) - elsif response_status.key?(:status) - interpret_status(response_status[:status]) - else - 302 - end - - url = case options - # The scheme name consist of a letter followed by any combination of - # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") - # characters; and is terminated by a colon (":"). - when %r{^\w[\w\d+.-]*:.*} - options - when String - request.protocol + request.host_with_port + options - when :back - raise RedirectBackError unless refer = request.headers["Referer"] - refer - else - url_for(options) - end - - super(url, status) - end end end diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 3caf759032..d784138ebe 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -30,12 +30,15 @@ module ActionController #:nodoc: # config.action_controller.cache_store = MyOwnStore.new("parameter") module Caching extend ActiveSupport::Concern + extend ActiveSupport::Autoload - autoload :Actions, 'action_controller/caching/actions' - autoload :Fragments, 'action_controller/caching/fragments' - autoload :Pages, 'action_controller/caching/pages' - autoload :Sweeper, 'action_controller/caching/sweeping' - autoload :Sweeping, 'action_controller/caching/sweeping' + eager_autoload do + autoload :Actions + autoload :Fragments + autoload :Pages + autoload :Sweeper, 'action_controller/caching/sweeping' + autoload :Sweeping, 'action_controller/caching/sweeping' + end included do @@cache_store = nil diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb index e04da42637..cf02757cf6 100644 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb @@ -13,11 +13,6 @@ module ActionController # Run prepare callbacks before every request in development mode self.prepare_each_request = true - # Development mode callbacks - ActionDispatch::Callbacks.before_dispatch do |app| - ActionController::Routing::Routes.reload - end - ActionDispatch::Callbacks.after_dispatch do # Cleanup the application before processing the current request. ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 60b3f9a89b..b436de9878 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -45,7 +45,7 @@ module ActionController # The details below can be overridden to support a specific # Request and Response object. The default ActionController::Base - # implementation includes RackConvenience, which makes a request + # implementation includes RackDelegation, which makes a request # and response object available. You might wish to control the # environment and response manually for performance reasons. @@ -57,7 +57,7 @@ module ActionController end # Basic implementations for content_type=, location=, and headers are - # provided to reduce the dependency on the RackConvenience module + # provided to reduce the dependency on the RackDelegation module # in Renderer and Redirector. def content_type=(type) @@ -68,6 +68,10 @@ module ActionController headers["Location"] = url end + def status=(status) + @_status = Rack::Utils.status_code(status) + end + # :api: private def dispatch(name, env) @_env = env @@ -92,6 +96,7 @@ module ActionController def initialize(controller, action) @controller, @action = controller, action + @_formats = [Mime::HTML] end def call(env) diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb index e58df69172..f73f635b0d 100644 --- a/actionpack/lib/action_controller/metal/benchmarking.rb +++ b/actionpack/lib/action_controller/metal/benchmarking.rb @@ -53,7 +53,6 @@ module ActionController #:nodoc: log_message << " [#{complete_request_uri rescue "unknown"}]" logger.info(log_message) - response.headers["X-Runtime"] = "%.0f" % ms else super end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index c251d79f4e..a90f798cd5 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -1,5 +1,5 @@ module ActionController - module Rails2Compatibility + module Compatibility extend ActiveSupport::Concern class ::ActionController::ActionControllerError < StandardError #:nodoc: @@ -46,11 +46,8 @@ module ActionController cattr_accessor :use_accept_header self.use_accept_header = true - cattr_accessor :page_cache_directory self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - cattr_reader :cache_store - cattr_accessor :consider_all_requests_local self.consider_all_requests_local = true @@ -116,7 +113,7 @@ module ActionController 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 diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 5156fbc1d5..61e7ece90d 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -2,7 +2,7 @@ module ActionController module ConditionalGet extend ActiveSupport::Concern - include RackConvenience + include RackDelegation include Head # Sets the etag, last_modified, or both on the response and renders a diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index 6855ca1478..8d5f0d7199 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -46,60 +46,152 @@ module ActionController #:nodoc: module Cookies extend ActiveSupport::Concern - include RackConvenience + include RackDelegation included do helper_method :cookies + cattr_accessor :cookie_verifier_secret end - protected - # Returns the cookie container, which operates as described above. - def cookies - @cookies ||= CookieJar.build(request, response) + protected + # Returns the cookie container, which operates as described above. + def cookies + @cookies ||= CookieJar.build(request, response) + end end - end - class CookieJar < Hash #:nodoc: - def self.build(request, response) - new.tap do |hash| - hash.update(request.cookies) - hash.response = response + class CookieJar < Hash #:nodoc: + def self.build(request, response) + new.tap do |hash| + hash.update(request.cookies) + hash.response = response + end end - end - attr_accessor :response + attr_accessor :response - # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. - def [](name) - super(name.to_s) - end + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. + def [](name) + super(name.to_s) + end + + # Sets the cookie named +name+. The second argument may be the very cookie + # value, or a hash of options as documented above. + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + value = options[:value] + else + value = options + options = { :value => value } + end - # Sets the cookie named +name+. The second argument may be the very cookie - # value, or a hash of options as documented above. - def []=(key, options) - if options.is_a?(Hash) + super(key.to_s, value) + + options[:path] ||= "/" + response.set_cookie(key, options) + end + + # Removes the cookie on the client machine by setting the value to an empty string + # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in + # an options hash to delete cookies with extra data such as a <tt>:path</tt>. + def delete(key, options = {}) options.symbolize_keys! - value = options[:value] - else - value = options - options = { :value => value } + options[:path] ||= "/" + value = super(key.to_s) + response.delete_cookie(key, options) + value end - super(key.to_s, value) - - options[:path] ||= "/" - response.set_cookie(key, options) + # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: + # + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # + # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. + # + # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: + # + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + def permanent + @permanent ||= PermanentCookieJar.new(self) + end + + # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from + # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed + # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will + # be raised. + # + # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret. + # + # Example: + # + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # + # cookies.signed[:discount] # => 45 + def signed + @signed ||= SignedCookieJar.new(self) + end end - - # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in - # an options hash to delete cookies with extra data such as a <tt>:path</tt>. - def delete(key, options = {}) - options.symbolize_keys! - options[:path] ||= "/" - value = super(key.to_s) - response.delete_cookie(key, options) - value + + class PermanentCookieJar < CookieJar #:nodoc: + def initialize(parent_jar) + @parent_jar = parent_jar + end + + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { :value => options } + end + + options[:expires] = 20.years.from_now + @parent_jar[key] = options + end + + def signed + @signed ||= SignedCookieJar.new(self) + end + + def controller + @parent_jar.controller + end + + def method_missing(method, *arguments, &block) + @parent_jar.send(method, *arguments, &block) + end end + + class SignedCookieJar < CookieJar #:nodoc: + def initialize(parent_jar) + unless ActionController::Base.cookie_verifier_secret + raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies" + end + + @parent_jar = parent_jar + @verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret) + end + + def [](name) + @verifier.verify(@parent_jar[name]) + end + + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + options[:value] = @verifier.generate(options[:value]) + else + options = { :value => @verifier.generate(options) } + end + + @parent_jar[key] = options + end + + def method_missing(method, *arguments, &block) + @parent_jar.send(method, *arguments, &block) + end end end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index b9d23da3e0..07024d0a9a 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -18,18 +18,9 @@ module ActionController def initialize(*allowed_methods) super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") - @allowed_methods = allowed_methods - end - - def allowed_methods_header - allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', ' - end - - def handle_response!(response) - response.headers['Allow'] ||= allowed_methods_header end end - + class NotImplemented < MethodNotAllowed #:nodoc: end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index f43900faa0..581ff6109e 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -28,7 +28,9 @@ module ActionController #:nodoc: module Flash extend ActiveSupport::Concern - include Session + included do + helper_method :alert, :notice + end class FlashNow #:nodoc: def initialize(flash) @@ -133,10 +135,44 @@ module ActionController #:nodoc: Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } return key ? self[key] : self end + end + + # Access the contents of the flash. Use <tt>flash["notice"]</tt> to + # read a notice you put there or <tt>flash["notice"] = "hello"</tt> + # to put a new one. + def flash #:doc: + unless @_flash + @_flash = session["flash"] || FlashHash.new + @_flash.sweep end + @_flash + end + + # Convenience accessor for flash[:alert] + def alert + flash[:alert] + end + + # Convenience accessor for flash[:alert]= + def alert=(message) + flash[:alert] = message + end + + # Convenience accessor for flash[:notice] + def notice + flash[:notice] + end + + # Convenience accessor for flash[:notice]= + def notice=(message) + flash[:notice] = message + end + + protected def process_action(method_name) + @_flash = nil super @_flash.store(session) if @_flash @_flash = nil @@ -147,16 +183,20 @@ module ActionController #:nodoc: @_flash = nil end - # Access the contents of the flash. Use <tt>flash["notice"]</tt> to - # read a notice you put there or <tt>flash["notice"] = "hello"</tt> - # to put a new one. - def flash #:doc: - unless @_flash - @_flash = session["flash"] || FlashHash.new - @_flash.sweep + def redirect_to(options = {}, response_status_and_flash = {}) #:doc: + if alert = response_status_and_flash.delete(:alert) + flash[:alert] = alert + end + + if notice = response_status_and_flash.delete(:notice) + flash[:notice] = notice + end + + if other_flashes = response_status_and_flash.delete(:flash) + flash.update(other_flashes) end - @_flash + super(options, response_status_and_flash) end end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 68fa0a0402..c82d9cf369 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,5 +1,7 @@ module ActionController module Head + include UrlFor + # Return a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. # This allows you to easily return a response that consists only of @@ -21,7 +23,10 @@ module ActionController headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s end - render :nothing => true, :status => status, :location => location + self.status = status + self.location = url_for(location) if location + self.content_type = Mime[formats.first] + self.response_body = " " end end end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index b4325e24ad..d0402e5bad 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -52,7 +52,7 @@ module ActionController included do # Set the default directory for helpers extlib_inheritable_accessor(:helpers_dir) do - defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers" + defined?(Rails.root) ? "#{Rails.root}/app/helpers" : "app/helpers" end end diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index cc7088248a..f44498a884 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -158,7 +158,7 @@ module ActionController module Layouts extend ActiveSupport::Concern - include ActionController::RenderingController + include ActionController::Rendering include AbstractController::Layouts module ClassMethods diff --git a/actionpack/lib/action_controller/metal/logger.rb b/actionpack/lib/action_controller/metal/logger.rb new file mode 100644 index 0000000000..956d7dd371 --- /dev/null +++ b/actionpack/lib/action_controller/metal/logger.rb @@ -0,0 +1,34 @@ +require 'abstract_controller/logger' + +module ActionController + module Logger + # Override process_action in the AbstractController::Base + # to log details about the method. + def process_action(action) + result = ActiveSupport::Notifications.instrument(:process_action, + :controller => self, :action => action) do + super + end + + if logger + log = AbstractController::Logger::DelayedLog.new do + "\n\nProcessing #{self.class.name}\##{action_name} " \ + "to #{request.formats} (for #{request_origin}) " \ + "[#{request.method.to_s.upcase}]" + end + + logger.info(log) + end + + result + end + + private + + # Returns the request origin with the IP and time. This needs to be cached, + # otherwise we would get different results for each time it calls. + def request_origin + @request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}" + end + end +end diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 131d20114d..bb55383631 100644 --- a/actionpack/lib/action_controller/metal/rack_convenience.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -1,8 +1,12 @@ +require 'action_dispatch/http/request' +require 'action_dispatch/http/response' + module ActionController - module RackConvenience + module RackDelegation extend ActiveSupport::Concern included do + delegate :session, :to => "@_request" delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :to => "@_response" attr_internal :request @@ -23,5 +27,9 @@ module ActionController response.body = body if response super end + + def reset_session + @_request.reset_session + end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb new file mode 100644 index 0000000000..7a2f9a6fc5 --- /dev/null +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -0,0 +1,90 @@ +module ActionController + class RedirectBackError < AbstractController::Error #:nodoc: + DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Redirecting + extend ActiveSupport::Concern + include AbstractController::Logger + + # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: + # + # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+. + # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection. + # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string. + # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places. + # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt> + # + # Examples: + # redirect_to :action => "show", :id => 5 + # redirect_to post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to articles_url + # redirect_to :back + # + # The redirection happens as a "302 Moved" header unless otherwise specified. + # + # Examples: + # redirect_to post_url(@post), :status => :found + # redirect_to :action=>'atom', :status => :moved_permanently + # redirect_to post_url(@post), :status => 301 + # redirect_to :action=>'atom', :status => 302 + # + # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names + # +alert+ and +notice+ as well as a general purpose +flash+ bucket. + # + # Examples: + # redirect_to post_url(@post), :alert => "Watch it, mister!" + # redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road" + # redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id } + # redirect_to { :action=>'atom' }, :alert => "Something serious happened" + # + # When using <tt>redirect_to :back</tt>, if there is no referrer, + # RedirectBackError will be raised. You may specify some fallback + # behavior for this case by rescuing RedirectBackError. + def redirect_to(options = {}, response_status = {}) #:doc: + raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + raise AbstractController::DoubleRenderError if response_body + + self.status = _extract_redirect_to_status(options, response_status) + self.location = _compute_redirect_to_location(options) + self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>" + + logger.info("Redirected to #{location}") if logger && logger.info? + end + + private + def _extract_redirect_to_status(options, response_status) + status = if options.is_a?(Hash) && options.key?(:status) + Rack::Utils.status_code(options.delete(:status)) + elsif response_status.key?(:status) + Rack::Utils.status_code(response_status[:status]) + else + 302 + end + end + + def _compute_redirect_to_location(options) + case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + when %r{^\w[\w\d+.-]*:.*} + options + when String + request.protocol + request.host_with_port + options + when :back + raise RedirectBackError unless refer = request.headers["Referer"] + refer + else + url_for(options) + end.gsub(/[\r\n]/, '') + end + end +end diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb deleted file mode 100644 index b55f5e7bfc..0000000000 --- a/actionpack/lib/action_controller/metal/redirector.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActionController - class RedirectBackError < AbstractController::Error #:nodoc: - DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' - - def initialize(message = nil) - super(message || DEFAULT_MESSAGE) - end - end - - module Redirector - extend ActiveSupport::Concern - include AbstractController::Logger - - def redirect_to(url, status) #:doc: - raise AbstractController::DoubleRenderError if response_body - logger.info("Redirected to #{url}") if logger && logger.info? - self.status = status - self.location = url.gsub(/[\r\n]/, '') - self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(url)}\">redirected</a>.</body></html>" - end - end -end diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb deleted file mode 100644 index 0d69ca10df..0000000000 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ /dev/null @@ -1,103 +0,0 @@ -module ActionController - module RenderOptions - extend ActiveSupport::Concern - - included do - extlib_inheritable_accessor :_renderers - self._renderers = [] - end - - module ClassMethods - def _write_render_options - renderers = _renderers.map do |r| - <<-RUBY_EVAL - if options.key?(:#{r}) - _process_options(options) - return render_#{r}(options[:#{r}], options) - end - RUBY_EVAL - end - - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _handle_render_options(options) - #{renderers.join} - end - RUBY_EVAL - end - - def _add_render_option(name) - _renderers << name - _write_render_options - end - end - - def render_to_body(options) - _handle_render_options(options) || super - end - end - - module RenderOption #:nodoc: - def self.extended(base) - base.extend ActiveSupport::Concern - base.send :include, ::ActionController::RenderOptions - - def base.register_renderer(name) - included { _add_render_option(name) } - end - end - end - - module RenderOptions - module Json - extend RenderOption - register_renderer :json - - def render_json(json, options) - json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - self.response_body = json - end - end - - module Js - extend RenderOption - register_renderer :js - - def render_js(js, options) - self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js : js - end - end - - module Xml - extend RenderOption - register_renderer :xml - - def render_xml(xml, options) - self.content_type ||= Mime::XML - self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml - end - end - - module RJS - extend RenderOption - register_renderer :update - - def render_update(proc, options) - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) - self.content_type = Mime::JS - self.response_body = generator.to_s - end - end - - module All - extend ActiveSupport::Concern - - include ActionController::RenderOptions::Json - include ActionController::RenderOptions::Js - include ActionController::RenderOptions::Xml - include ActionController::RenderOptions::RJS - end - end -end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb new file mode 100644 index 0000000000..c1ba47927a --- /dev/null +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -0,0 +1,91 @@ +module ActionController + def self.add_renderer(key, &block) + Renderers.add(key, &block) + end + + module Renderers + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor :_renderers + self._renderers = {} + end + + module ClassMethods + def _write_render_options + renderers = _renderers.map do |name, value| + <<-RUBY_EVAL + if options.key?(:#{name}) + _process_options(options) + return _render_option_#{name}(options[:#{name}], options) + end + RUBY_EVAL + end + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _handle_render_options(options) + #{renderers.join} + end + RUBY_EVAL + end + + def use_renderers(*args) + args.each do |key| + _renderers[key] = RENDERERS[key] + end + _write_render_options + end + alias use_renderer use_renderers + end + + def render_to_body(options) + _handle_render_options(options) || super + end + + RENDERERS = {} + def self.add(key, &block) + define_method("_render_option_#{key}", &block) + RENDERERS[key] = block + All._write_render_options + end + + module All + extend ActiveSupport::Concern + include Renderers + + INCLUDED = [] + included do + self._renderers = RENDERERS + _write_render_options + INCLUDED << self + end + + def self._write_render_options + INCLUDED.each(&:_write_render_options) + end + end + + add :json do |json, options| + json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) + json = "#{options[:callback]}(#{json})" unless options[:callback].blank? + self.content_type ||= Mime::JSON + self.response_body = json + end + + add :js do |js, options| + self.content_type ||= Mime::JS + self.response_body = js.respond_to?(:to_js) ? js.to_js : js + end + + add :xml do |xml, options| + self.content_type ||= Mime::XML + self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml + end + + add :update do |proc, options| + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) + self.content_type = Mime::JS + self.response_body = generator.to_s + end + end +end diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering.rb index 237299cd30..20eb524e50 100644 --- a/actionpack/lib/action_controller/metal/rendering_controller.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,9 +1,9 @@ module ActionController - module RenderingController + module Rendering extend ActiveSupport::Concern included do - include AbstractController::RenderingController + include AbstractController::Rendering include AbstractController::LocalizedCache end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 173df79ee7..2826b1e34c 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -5,7 +5,7 @@ module ActionController #:nodoc: module RequestForgeryProtection extend ActiveSupport::Concern - include AbstractController::Helpers, Session + include AbstractController::Helpers included do # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ @@ -19,31 +19,31 @@ module ActionController #:nodoc: helper_method :form_authenticity_token helper_method :protect_against_forgery? end - - # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current - # web application, not a forged link from another site, is done by embedding a token based on a random + + # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current + # web application, not a forged link from another site, is done by embedding a token based on a random # string stored in the session (which an attacker wouldn't know) in all forms and Ajax requests generated - # by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript - # requests are checked, so this will not protect your XML API (presumably you'll have a different - # authentication scheme there anyway). Also, GET requests are not protected as these should be + # by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript + # requests are checked, so this will not protect your XML API (presumably you'll have a different + # authentication scheme there anyway). Also, GET requests are not protected as these should be # idempotent anyway. # # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an - # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the + # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the # error message in production by editing public/422.html. A call to this method in ApplicationController is # generated by default in post-Rails 2.0 applications. # - # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form - # manually (without the use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to - # include a hidden field named like that and set its value to what is returned by + # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form + # manually (without the use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to + # include a hidden field named like that and set its value to what is returned by # <tt>form_authenticity_token</tt>. # - # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails + # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails # 1.x, add this to config/environments/test.rb: # # # Disable request forgery protection in test environment # config.action_controller.allow_forgery_protection = false - # + # # == Learn more about CSRF (Cross-Site Request Forgery) attacks # # Here are some resources: @@ -52,11 +52,11 @@ module ActionController #:nodoc: # # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. # There are a few guidelines you should follow: - # + # # * Keep your GET requests safe and idempotent. More reading material: # * http://www.xml.com/pub/a/2002/04/24/deviant.html # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 - # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look + # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look # for "Expires: at end of session" # module ClassMethods @@ -92,7 +92,7 @@ module ActionController #:nodoc: # * is it a GET request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? def verified_request? - !protect_against_forgery? || request.forgery_whitelisted? || + !protect_against_forgery? || request.forgery_whitelisted? || form_authenticity_token == params[request_forgery_protection_token] end diff --git a/actionpack/lib/action_controller/metal/rescuable.rb b/actionpack/lib/action_controller/metal/rescue.rb index bbca1b2179..bbca1b2179 100644 --- a/actionpack/lib/action_controller/metal/rescuable.rb +++ b/actionpack/lib/action_controller/metal/rescue.rb diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index e8e88e7479..cb0e600871 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -80,6 +80,11 @@ module ActionController #:nodoc: class Responder attr_reader :controller, :request, :format, :resource, :resources, :options + ACTIONS_FOR_VERBS = { + :post => :new, + :put => :edit + } + def initialize(controller, resources, options={}) @controller = controller @request = controller.request @@ -102,9 +107,14 @@ module ActionController #:nodoc: # not defined, call to_format. # def self.call(*args) - responder = new(*args) - method = :"to_#{responder.format}" - responder.respond_to?(method) ? responder.send(method) : responder.to_format + new(*args).respond + end + + # Main entry point for responder responsible to dispatch to the proper format. + # + def respond + method = :"to_#{format}" + respond_to?(method) ? send(method) : to_format end # HTML format does not render the resource, it always attempt to render a @@ -133,7 +143,7 @@ module ActionController #:nodoc: def navigation_behavior(error) if get? raise error - elsif has_errors? + elsif has_errors? && default_action render :action => default_action else redirect_to resource_location @@ -204,7 +214,7 @@ module ActionController #:nodoc: # the verb is post. # def default_action - @action || (request.post? ? :new : :edit) + @action ||= ACTIONS_FOR_VERBS[request.method] end end end diff --git a/actionpack/lib/action_controller/metal/session.rb b/actionpack/lib/action_controller/metal/session.rb deleted file mode 100644 index bcedd6e1c7..0000000000 --- a/actionpack/lib/action_controller/metal/session.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActionController - module Session - extend ActiveSupport::Concern - - include RackConvenience - - def session - @_request.session - end - - def reset_session - @_request.reset_session - end - end -end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 43c661bef4..288b5d7c99 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -4,7 +4,7 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include ActionController::RenderingController + include ActionController::Rendering DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index a4a1116d9e..c193a5eff4 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -2,7 +2,7 @@ module ActionController module Testing extend ActiveSupport::Concern - include RackConvenience + include RackDelegation # OMG MEGA HAX def process_with_new_base_test(request, response) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 14c6523045..8c3810ebcb 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -2,7 +2,7 @@ module ActionController module UrlFor extend ActiveSupport::Concern - include RackConvenience + include RackDelegation # 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: diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index 500cced539..bce942b588 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -2,7 +2,7 @@ module ActionController #:nodoc: module Verification #:nodoc: extend ActiveSupport::Concern - include AbstractController::Callbacks, Session, Flash, RenderingController + include AbstractController::Callbacks, Flash, Rendering # This module provides a class-level method for specifying that certain # actions are guarded against being called without certain prerequisites @@ -35,7 +35,7 @@ module ActionController #:nodoc: # :add_flash => { "alert" => "Failed to create your message" }, # :redirect_to => :category_url # - # Note that these prerequisites are not business rules. They do not examine + # Note that these prerequisites are not business rules. They do not examine # the content of the session or the parameters. That level of validation should # be encapsulated by your domain model or helper methods in the controller. module ClassMethods @@ -43,40 +43,40 @@ module ActionController #:nodoc: # the user is redirected to a different action. The +options+ parameter # is a hash consisting of the following key/value pairs: # - # <tt>:params</tt>:: - # a single key or an array of keys that must be in the <tt>params</tt> + # <tt>:params</tt>:: + # a single key or an array of keys that must be in the <tt>params</tt> # hash in order for the action(s) to be safely called. - # <tt>:session</tt>:: - # a single key or an array of keys that must be in the <tt>session</tt> + # <tt>:session</tt>:: + # a single key or an array of keys that must be in the <tt>session</tt> # in order for the action(s) to be safely called. - # <tt>:flash</tt>:: - # a single key or an array of keys that must be in the flash in order + # <tt>:flash</tt>:: + # a single key or an array of keys that must be in the flash in order # for the action(s) to be safely called. - # <tt>:method</tt>:: - # a single key or an array of keys--any one of which must match the - # current request method in order for the action(s) to be safely called. - # (The key should be a symbol: <tt>:get</tt> or <tt>:post</tt>, for + # <tt>:method</tt>:: + # a single key or an array of keys--any one of which must match the + # current request method in order for the action(s) to be safely called. + # (The key should be a symbol: <tt>:get</tt> or <tt>:post</tt>, for # example.) - # <tt>:xhr</tt>:: - # true/false option to ensure that the request is coming from an Ajax - # call or not. - # <tt>:add_flash</tt>:: - # a hash of name/value pairs that should be merged into the session's + # <tt>:xhr</tt>:: + # true/false option to ensure that the request is coming from an Ajax + # call or not. + # <tt>:add_flash</tt>:: + # a hash of name/value pairs that should be merged into the session's # flash if the prerequisites cannot be satisfied. - # <tt>:add_headers</tt>:: - # a hash of name/value pairs that should be merged into the response's + # <tt>:add_headers</tt>:: + # a hash of name/value pairs that should be merged into the response's # headers hash if the prerequisites cannot be satisfied. - # <tt>:redirect_to</tt>:: - # the redirection parameters to be used when redirecting if the - # prerequisites cannot be satisfied. You can redirect either to named + # <tt>:redirect_to</tt>:: + # the redirection parameters to be used when redirecting if the + # prerequisites cannot be satisfied. You can redirect either to named # route or to the action in some controller. - # <tt>:render</tt>:: + # <tt>:render</tt>:: # the render parameters to be used when the prerequisites cannot be satisfied. - # <tt>:only</tt>:: - # only apply this verification to the actions specified in the associated + # <tt>:only</tt>:: + # only apply this verification to the actions specified in the associated # array (may also be a single value). - # <tt>:except</tt>:: - # do not apply this verification to the actions specified in the associated + # <tt>:except</tt>:: + # do not apply this verification to the actions specified in the associated # array (may also be a single value). def verify(options={}) before_filter :only => options[:only], :except => options[:except] do @@ -94,31 +94,31 @@ module ActionController #:nodoc: apply_remaining_actions(options) unless performed? end end - + def prereqs_invalid?(options) # :nodoc: - verify_presence_of_keys_in_hash_flash_or_params(options) || - verify_method(options) || + verify_presence_of_keys_in_hash_flash_or_params(options) || + verify_method(options) || verify_request_xhr_status(options) end - + def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc: [*options[:params] ].find { |v| v && params[v.to_sym].nil? } || [*options[:session]].find { |v| session[v].nil? } || [*options[:flash] ].find { |v| flash[v].nil? } end - + def verify_method(options) # :nodoc: [*options[:method]].all? { |v| request.method != v.to_sym } if options[:method] end - + def verify_request_xhr_status(options) # :nodoc: request.xhr? != options[:xhr] unless options[:xhr].nil? end - + def apply_redirect_to(redirect_to_option) # :nodoc: (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option end - + def apply_remaining_actions(options) # :nodoc: case when options[:render] ; render(options[:render]) diff --git a/actionpack/lib/action_controller/notifications.rb b/actionpack/lib/action_controller/notifications.rb deleted file mode 100644 index 1a4f29e0e2..0000000000 --- a/actionpack/lib/action_controller/notifications.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'active_support/notifications' - -ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args| - event = ActiveSupport::Notifications::Event.new(*args) - - if logger = ActionController::Base.logger - human_name = event.name.to_s.humanize - logger.info("#{human_name} (%.1fms)" % event.duration) - end -end diff --git a/actionpack/lib/action_controller/rails.rb b/actionpack/lib/action_controller/rails.rb new file mode 100644 index 0000000000..36a52b3149 --- /dev/null +++ b/actionpack/lib/action_controller/rails.rb @@ -0,0 +1,102 @@ +module ActionController + class Plugin < Rails::Plugin + plugin_name :action_controller + + initializer "action_controller.set_configs" do |app| + app.config.action_controller.each do |k,v| + ActionController::Base.send "#{k}=", v + end + end + + # TODO: ActionController::Base.logger should delegate to its own config.logger + initializer "action_controller.logger" do + ActionController::Base.logger ||= Rails.logger + end + + # Routing must be initialized after plugins to allow the former to extend the routes + # --- + # If Action Controller is not one of the loaded frameworks (Configuration#frameworks) + # this does nothing. Otherwise, it loads the routing definitions and sets up + # loading module used to lazily load controllers (Configuration#controller_paths). + initializer "action_controller.initialize_routing" do |app| + app.route_configuration_files << app.config.routes_configuration_file + app.route_configuration_files << app.config.builtin_routes_configuration_file + app.reload_routes! + end + + # Include middleware to serve up static assets + initializer "action_controller.initialize_static_server" do |app| + if app.config.serve_static_assets + app.config.middleware.use(ActionDispatch::Static, Rails.public_path) + end + end + + initializer "action_controller.initialize_middleware_stack" do |app| + middleware = app.config.middleware + middleware.use(::Rack::Lock, :if => lambda { ActionController::Base.allow_concurrency }) + middleware.use(::Rack::Runtime) + middleware.use(ActionDispatch::ShowExceptions, lambda { ActionController::Base.consider_all_requests_local }) + middleware.use(ActionDispatch::Callbacks, lambda { ActionController::Dispatcher.prepare_each_request }) + middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) + middleware.use(ActionDispatch::ParamsParser) + middleware.use(::Rack::MethodOverride) + middleware.use(::Rack::Head) + middleware.use(ActionDispatch::StringCoercion) + end + + initializer "action_controller.initialize_framework_caches" do + ActionController::Base.cache_store ||= RAILS_CACHE + end + + # Sets +ActionController::Base#view_paths+ and +ActionMailer::Base#template_root+ + # (but only for those frameworks that are to be loaded). If the framework's + # paths have already been set, it is not changed, otherwise it is + # set to use Configuration#view_path. + initializer "action_controller.initialize_framework_views" do |app| + # TODO: this should be combined with the logic for default config.action_controller.view_paths + view_path = ActionView::PathSet.type_cast(app.config.view_path, app.config.cache_classes) + ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank? + end + + initializer "action_controller.initialize_metal" do |app| + Rails::Rack::Metal.requested_metals = app.config.metals + + app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", + Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?) + end + + # # Prepare dispatcher callbacks and run 'prepare' callbacks + initializer "action_controller.prepare_dispatcher" do |app| + # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. + require 'rails/dispatcher' + + Dispatcher.define_dispatcher_callbacks(app.config.cache_classes) + + unless app.config.cache_classes + # Setup dev mode route reloading + routes_last_modified = app.routes_changed_at + reload_routes = lambda do + unless app.routes_changed_at == routes_last_modified + routes_last_modified = app.routes_changed_at + app.reload_routes! + end + end + ActionDispatch::Callbacks.before_dispatch { |callbacks| reload_routes.call } + end + end + + initializer "action_controller.notifications" do |app| + require 'active_support/notifications' + + ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args| + event = ActiveSupport::Notifications::Event.new(*args) + + if logger = ActionController::Base.logger + human_name = event.name.to_s.humanize + logger.info("#{human_name} (%.1fms)" % event.duration) + end + end + end + + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 01a55fe930..398ea52495 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,5 +1,6 @@ require 'active_support/test_case' require 'rack/session/abstract/id' +require 'action_controller/metal/testing' module ActionController class TestRequest < ActionDispatch::TestRequest #:nodoc: @@ -180,7 +181,7 @@ module ActionController # # assert_redirected_to page_url(:title => 'foo') class TestCase < ActiveSupport::TestCase - include TestProcess + include ActionDispatch::TestProcess # Executes a request simulating GET HTTP method and set/volley the response def get(action, parameters = nil, session = nil, flash = nil) diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb deleted file mode 100644 index 323cce6a2f..0000000000 --- a/actionpack/lib/action_controller/testing/process.rb +++ /dev/null @@ -1,111 +0,0 @@ -require 'active_support/core_ext/object/conversions' -require "rack/test" - -module ActionController #:nodoc: - # Essentially generates a modified Tempfile object similar to the object - # you'd get from the standard library CGI module in a multipart - # request. This means you can use an ActionController::TestUploadedFile - # object in the params of a test request in order to simulate - # a file upload. - # - # Usage example, within a functional test: - # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png') - # - # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows): - # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary) - TestUploadedFile = Rack::Test::UploadedFile - - module TestProcess - def assigns(key = nil) - assigns = {} - @controller.instance_variable_names.each do |ivar| - next if ActionController::Base.protected_instance_variables.include?(ivar) - assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar) - end - - key.nil? ? assigns : assigns[key.to_s] - end - - def session - @request.session - end - - def flash - @request.flash - end - - def cookies - @request.cookies.merge(@response.cookies) - end - - def redirect_to_url - @response.redirect_url - end - - def html_document - xml = @response.content_type =~ /xml$/ - @html_document ||= HTML::Document.new(@response.body, false, xml) - end - - def find_tag(conditions) - html_document.find(conditions) - end - - def find_all_tag(conditions) - html_document.find_all(conditions) - end - - def method_missing(selector, *args, &block) - if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector) - @controller.send(selector, *args, &block) - else - super - end - end - - # Shortcut for <tt>ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>: - # - # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') - # - # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter. - # This will not affect other platforms: - # - # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) - def fixture_file_upload(path, mime_type = nil, binary = false) - fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path) - ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary) - end - - # A helper to make it easier to test different route configurations. - # This method temporarily replaces ActionController::Routing::Routes - # with a new RouteSet instance. - # - # The new instance is yielded to the passed block. Typically the block - # will create some routes using <tt>map.draw { map.connect ... }</tt>: - # - # with_routing do |set| - # set.draw do |map| - # map.connect ':controller/:action/:id' - # assert_equal( - # ['/content/10/show', {}], - # map.generate(:controller => 'content', :id => 10, :action => 'show') - # end - # end - # 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 - 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 - end - end -end diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb index f622d195ee..879b31e60e 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner.rb @@ -1,16 +1,20 @@ $LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner" module HTML - autoload :CDATA, 'html/node' - autoload :Document, 'html/document' - autoload :FullSanitizer, 'html/sanitizer' - autoload :LinkSanitizer, 'html/sanitizer' - autoload :Node, 'html/node' - autoload :Sanitizer, 'html/sanitizer' - autoload :Selector, 'html/selector' - autoload :Tag, 'html/node' - autoload :Text, 'html/node' - autoload :Tokenizer, 'html/tokenizer' - autoload :Version, 'html/version' - autoload :WhiteListSanitizer, 'html/sanitizer' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :CDATA, 'html/node' + autoload :Document, 'html/document' + autoload :FullSanitizer, 'html/sanitizer' + autoload :LinkSanitizer, 'html/sanitizer' + autoload :Node, 'html/node' + autoload :Sanitizer, 'html/sanitizer' + autoload :Selector, 'html/selector' + autoload :Tag, 'html/node' + autoload :Text, 'html/node' + autoload :Tokenizer, 'html/tokenizer' + autoload :Version, 'html/version' + autoload :WhiteListSanitizer, 'html/sanitizer' + end end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 259814a322..0696cb017c 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -21,6 +21,10 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +require 'active_support/ruby/shim' + require 'rack' module Rack @@ -28,29 +32,25 @@ module Rack end module ActionDispatch - autoload :Request, 'action_dispatch/http/request' - autoload :Response, 'action_dispatch/http/response' - autoload :StatusCodes, 'action_dispatch/http/status_codes' - autoload :Utils, 'action_dispatch/http/utils' - - autoload :Callbacks, 'action_dispatch/middleware/callbacks' - autoload :MiddlewareStack, 'action_dispatch/middleware/stack' - autoload :ParamsParser, 'action_dispatch/middleware/params_parser' - autoload :Rescue, 'action_dispatch/middleware/rescue' - autoload :ShowExceptions, 'action_dispatch/middleware/show_exceptions' - autoload :Static, 'action_dispatch/middleware/static' - autoload :StringCoercion, 'action_dispatch/middleware/string_coercion' + extend ActiveSupport::Autoload - autoload :Routing, 'action_dispatch/routing' + autoload_under 'http' do + autoload :Request + autoload :Response + end - autoload :Assertions, 'action_dispatch/testing/assertions' - autoload :Integration, 'action_dispatch/testing/integration' - autoload :IntegrationTest, 'action_dispatch/testing/integration' - autoload :PerformanceTest, 'action_dispatch/testing/performance_test' - autoload :TestRequest, 'action_dispatch/testing/test_request' - autoload :TestResponse, 'action_dispatch/testing/test_response' + autoload_under 'middleware' do + autoload :Callbacks + autoload :Cascade + autoload :ParamsParser + autoload :Rescue + autoload :ShowExceptions + autoload :Static + autoload :StringCoercion + end - autoload :HTML, 'action_controller/vendor/html-scanner' + autoload :MiddlewareStack, 'action_dispatch/middleware/stack' + autoload :Routing module Http autoload :Headers, 'action_dispatch/http/headers' @@ -58,13 +58,18 @@ module ActionDispatch module Session autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' end + + autoload_under 'testing' do + autoload :Assertions + autoload :Integration + autoload :PerformanceTest + autoload :TestProcess + autoload :TestRequest + autoload :TestResponse + end end autoload :Mime, 'action_dispatch/http/mime_type' - -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift activesupport_path if File.directory?(activesupport_path) -require 'active_support' diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 6a52854961..6e8a5dcb8a 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -6,6 +6,7 @@ require 'active_support/memoizable' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' +require 'action_dispatch/http/headers' module ActionDispatch class Request < Rack::Request @@ -17,7 +18,7 @@ module ActionDispatch HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM - HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| + HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| define_method(env.sub(/^HTTP_/n, '').downcase) do @env[env] end @@ -117,7 +118,7 @@ module ActionDispatch end end end - + def if_modified_since if since = env['HTTP_IF_MODIFIED_SINCE'] Time.rfc2822(since) rescue nil @@ -464,6 +465,15 @@ EOM session['flash'] || {} end + # Returns the authorization header regardless of whether it was specified directly or through one of the + # proxy alternatives. + def authorization + @env['HTTP_AUTHORIZATION'] || + @env['X-HTTP_AUTHORIZATION'] || + @env['X_HTTP_AUTHORIZATION'] || + @env['REDIRECT_X_HTTP_AUTHORIZATION'] + 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/response.rb b/actionpack/lib/action_dispatch/http/response.rb index b3ed7c9d1a..8524bbd993 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -33,7 +33,6 @@ module ActionDispatch # :nodoc: # end class Response < Rack::Response attr_accessor :request, :blank - attr_reader :cache_control attr_writer :header, :sending_file alias_method :headers=, :header= @@ -50,6 +49,9 @@ module ActionDispatch # :nodoc: @body, @cookie = [], [] @sending_file = false + @blank = false + @etag = nil + yield self if block_given? end @@ -57,14 +59,8 @@ module ActionDispatch # :nodoc: @cache_control ||= {} end - def write(str) - s = str.to_s - @writer.call s - str - end - def status=(status) - @status = status.to_i + @status = Rack::Utils.status_code(status) end # The response code of the request @@ -78,7 +74,7 @@ module ActionDispatch # :nodoc: end def message - StatusCodes::STATUS_CODES[@status] + Rack::Utils::HTTP_STATUS_CODES[@status] end alias_method :status_message, :message @@ -149,18 +145,6 @@ module ActionDispatch # :nodoc: cattr_accessor(:default_charset) { "utf-8" } - def assign_default_content_type_and_charset! - return if headers[CONTENT_TYPE].present? - - @content_type ||= Mime::HTML - @charset ||= self.class.default_charset - - type = @content_type.to_s.dup - type << "; charset=#{@charset}" unless @sending_file - - headers[CONTENT_TYPE] = type - end - def to_a assign_default_content_type_and_charset! handle_conditional_get! @@ -263,6 +247,18 @@ module ActionDispatch # :nodoc: !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } end + def assign_default_content_type_and_charset! + return if headers[CONTENT_TYPE].present? + + @content_type ||= Mime::HTML + @charset ||= self.class.default_charset + + type = @content_type.to_s.dup + type << "; charset=#{@charset}" unless @sending_file + + headers[CONTENT_TYPE] = type + end + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" def set_conditional_cache_control! @@ -277,14 +273,13 @@ module ActionDispatch # :nodoc: max_age = control[:max_age] options = [] - options << "max-age=#{max_age}" if max_age + options << "max-age=#{max_age.to_i}" if max_age options << (control[:public] ? "public" : "private") options << "must-revalidate" if control[:must_revalidate] options.concat(extras) if extras headers["Cache-Control"] = options.join(", ") end - end end end diff --git a/actionpack/lib/action_dispatch/http/status_codes.rb b/actionpack/lib/action_dispatch/http/status_codes.rb deleted file mode 100644 index 5bac842ec1..0000000000 --- a/actionpack/lib/action_dispatch/http/status_codes.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'active_support/inflector' - -module ActionDispatch - module StatusCodes #:nodoc: - STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES.merge({ - 102 => "Processing", - 207 => "Multi-Status", - 226 => "IM Used", - 422 => "Unprocessable Entity", - 423 => "Locked", - 424 => "Failed Dependency", - 426 => "Upgrade Required", - 507 => "Insufficient Storage", - 510 => "Not Extended" - }).freeze - - # Provides a symbol-to-fixnum lookup for converting a symbol (like - # :created or :not_implemented) into its corresponding HTTP status - # code (like 200 or 501). - SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) { |hash, (code, message)| - hash[ActiveSupport::Inflector.underscore(message.gsub(/ /, "")).to_sym] = code - hash - }.freeze - - private - # Given a status parameter, determine whether it needs to be converted - # to a string. If it is a fixnum, use the STATUS_CODES hash to lookup - # the default message. If it is a symbol, use the SYMBOL_TO_STATUS_CODE - # hash to convert it. - def interpret_status(status) - case status - when Fixnum then - "#{status} #{STATUS_CODES[status]}".strip - when Symbol then - interpret_status(SYMBOL_TO_STATUS_CODE[status] || - "500 Unknown Status #{status.inspect}") - else - status.to_s - end - end - end -end diff --git a/actionpack/lib/action_dispatch/http/utils.rb b/actionpack/lib/action_dispatch/http/utils.rb deleted file mode 100644 index e04a39935e..0000000000 --- a/actionpack/lib/action_dispatch/http/utils.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActionDispatch - module Utils - # TODO: Pull this into rack core - # http://github.com/halorgium/rack/commit/feaf071c1de743fbd10bc316830180a9af607278 - def parse_config(config) - if config =~ /\.ru$/ - cfgfile = ::File.read(config) - if cfgfile[/^#\\(.*)/] - opts.parse! $1.split(/\s+/) - end - inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", - nil, config - else - require config - inner_app = Object.const_get(::File.basename(config, '.rb').capitalize) - end - end - module_function :parse_config - end -end diff --git a/actionpack/lib/action_dispatch/middleware/cascade.rb b/actionpack/lib/action_dispatch/middleware/cascade.rb new file mode 100644 index 0000000000..9f5c9891f0 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/cascade.rb @@ -0,0 +1,29 @@ +module ActionDispatch + class Cascade + def self.new(*apps) + apps = apps.flatten + + case apps.length + when 0 + raise ArgumentError, "app is required" + when 1 + apps.first + else + super(apps) + end + end + + def initialize(apps) + @apps = apps + end + + def call(env) + result = nil + @apps.each do |app| + result = app.call(env) + break unless result[1]["X-Cascade"] == "pass" + end + result + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 32ccb5c931..534390d4aa 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -1,4 +1,5 @@ require 'active_support/json' +require 'action_dispatch/http/request' module ActionDispatch class ParamsParser @@ -31,41 +32,39 @@ module ActionDispatch return false unless strategy case strategy - when Proc - strategy.call(request.raw_post) - when :xml_simple, :xml_node - request.body.size == 0 ? {} : Hash.from_xml(request.body).with_indifferent_access - when :yaml - YAML.load(request.body) - when :json - if request.body.size == 0 - {} - else - data = ActiveSupport::JSON.decode(request.body) - data = {:_json => data} unless data.is_a?(Hash) - data.with_indifferent_access - end + when Proc + strategy.call(request.raw_post) + when :xml_simple, :xml_node + request.body.size == 0 ? {} : Hash.from_xml(request.body).with_indifferent_access + when :yaml + YAML.load(request.body) + when :json + if request.body.size == 0 + {} else - false + data = ActiveSupport::JSON.decode(request.body) + data = {:_json => data} unless data.is_a?(Hash) + data.with_indifferent_access + end + else + false end rescue Exception => e # YAML, XML or Ruby code block errors logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" raise - { "body" => request.raw_post, - "content_type" => request.content_type, + { "body" => request.raw_post, + "content_type" => request.content_type, "content_length" => request.content_length, - "exception" => "#{e.message} (#{e.class})", - "backtrace" => e.backtrace } + "exception" => "#{e.message} (#{e.class})", + "backtrace" => e.backtrace } end def content_type_from_legacy_post_data_format_header(env) if x_post_format = env['HTTP_X_POST_DATA_FORMAT'] case x_post_format.to_s.downcase - when 'yaml' - return Mime::YAML - when 'xml' - return Mime::XML + when 'yaml' then return Mime::YAML + when 'xml' then return Mime::XML end end @@ -76,4 +75,4 @@ module ActionDispatch defined?(Rails.logger) ? Rails.logger : Logger.new($stderr) end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index c5c06f74a2..7d4f0998ce 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -1,4 +1,5 @@ require 'rack/utils' +require 'rack/request' module ActionDispatch module Session diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index bd552b458a..f27f22c7e7 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,4 +1,5 @@ -require "active_support/core_ext/hash/keys" +require 'active_support/core_ext/hash/keys' +require 'rack/request' module ActionDispatch module Session @@ -49,7 +50,7 @@ module ActionDispatch :expire_after => nil, :httponly => true }.freeze - + class OptionsHash < Hash def initialize(by, env, default_options) @session_data = env[CookieStore::ENV_SESSION_KEY] @@ -60,7 +61,7 @@ module ActionDispatch key == :id ? @session_data[:session_id] : super(key) end end - + ENV_SESSION_KEY = "rack.session".freeze ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze HTTP_SET_COOKIE = "Set-Cookie".freeze @@ -102,7 +103,7 @@ module ActionDispatch def call(env) env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) - + status, headers, body = @app.call(env) session_data = env[ENV_SESSION_KEY] diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 036deec6d2..4ebc8a2ab9 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,9 +1,8 @@ -require "active_support/core_ext/exception" +require 'active_support/core_ext/exception' +require 'action_dispatch/http/request' module ActionDispatch class ShowExceptions - include StatusCodes - LOCALHOST = '127.0.0.1'.freeze RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates') @@ -12,8 +11,7 @@ module ActionDispatch @@rescue_responses = Hash.new(:internal_server_error) @@rescue_responses.update({ 'ActionController::RoutingError' => :not_found, - # TODO: Clean this up after the switch - ActionController::UnknownAction.name => :not_found, + 'AbstractController::ActionNotFound' => :not_found, 'ActiveRecord::RecordNotFound' => :not_found, 'ActiveRecord::StaleObjectError' => :conflict, 'ActiveRecord::RecordInvalid' => :unprocessable_entity, @@ -28,8 +26,8 @@ module ActionDispatch @@rescue_templates.update({ 'ActionView::MissingTemplate' => 'missing_template', 'ActionController::RoutingError' => 'routing_error', - ActionController::UnknownAction.name => 'unknown_action', - 'ActionView::TemplateError' => 'template_error' + 'AbstractController::ActionNotFound' => 'unknown_action', + 'ActionView::Template::Error' => 'template_error' }) FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'}, @@ -104,7 +102,7 @@ module ActionDispatch end def status_code(exception) - interpret_status(@@rescue_responses[exception.class.name]).to_i + Rack::Utils.status_code(@@rescue_responses[exception.class.name]) end def render(status, body) @@ -119,7 +117,7 @@ module ActionDispatch return unless logger ActiveSupport::Deprecation.silence do - if ActionView::TemplateError === exception + if ActionView::Template::Error === exception logger.fatal(exception.to_s) else logger.fatal( diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb index f8f6b424ca..07b4919934 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb @@ -7,7 +7,7 @@ names = traces.collect {|name, trace| name} %> -<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p> +<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p> <div id="traces"> <% names.each do |name| %> diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 68ed1e3340..e99f979197 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -264,118 +264,11 @@ module ActionDispatch autoload :RouteSet, 'action_dispatch/routing/route_set' SEPARATORS = %w( / . ? ) - HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] - ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set - - # The root paths which may contain controller files - mattr_accessor :controller_paths - self.controller_paths = [] - # A helper module to hold URL related helpers. module Helpers include ActionController::PolymorphicRoutes end - - class << self - # Expects an array of controller names as the first argument. - # Executes the passed block with only the named controllers named available. - # This method is used in internal Rails testing. - def with_controllers(names) - prior_controllers = @possible_controllers - use_controllers! names - yield - ensure - use_controllers! prior_controllers - end - - # Returns an array of paths, cleaned of double-slashes and relative path references. - # * "\\\" and "//" become "\\" or "/". - # * "/foo/bar/../config" becomes "/foo/config". - # The returned array is sorted by length, descending. - def normalize_paths(paths) - # do the hokey-pokey of path normalization... - paths = paths.collect do |path| - path = path. - gsub("//", "/"). # replace double / chars with a single - gsub("\\\\", "\\"). # replace double \ chars with a single - gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it - - # eliminate .. paths where possible - re = %r{[^/\\]+[/\\]\.\.[/\\]} - path.gsub!(re, "") while path.match(re) - path - end - - # start with longest path, first - paths = paths.uniq.sort_by { |path| - path.length } - end - - # Returns the array of controller names currently available to ActionController::Routing. - def possible_controllers - unless @possible_controllers - @possible_controllers = [] - - paths = controller_paths.select { |path| File.directory?(path) && path != "." } - - seen_paths = Hash.new {|h, k| h[k] = true; false} - normalize_paths(paths).each do |load_path| - Dir["#{load_path}/**/*_controller.rb"].collect do |path| - next if seen_paths[path.gsub(%r{^\.[/\\]}, "")] - - controller_name = path[(load_path.length + 1)..-1] - - controller_name.gsub!(/_controller\.rb\Z/, '') - @possible_controllers << controller_name - end - end - - # remove duplicates - @possible_controllers.uniq! - end - @possible_controllers - end - - # Replaces the internal list of controllers available to ActionController::Routing with the passed argument. - # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ]) - def use_controllers!(controller_names) - @possible_controllers = controller_names - end - - # Returns a controller path for a new +controller+ based on a +previous+ controller path. - # Handles 4 scenarios: - # - # * stay in the previous controller: - # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion" - # - # * stay in the previous namespace: - # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts" - # - # * forced move to the root namespace: - # controller_relative_to( "/posts", "groups/discussion" ) # => "posts" - # - # * previous namespace is root: - # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts" - # - def controller_relative_to(controller, previous) - if controller.nil? then previous - elsif controller[0] == ?/ then controller[1..-1] - elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}" - else controller - end - end - end - - ActiveSupport::Inflector.module_eval do - # Ensures that routes are reloaded when Rails inflections are updated. - def inflections_with_route_reloading(&block) - returning(inflections_without_route_reloading(&block)) { - ActionDispatch::Routing::Routes.reload! if block_given? - } - end - - alias_method_chain :inflections, :route_reloading - end end end diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 0564ba9797..8ce6b2f6d5 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -113,8 +113,7 @@ module ActionDispatch end end - possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) } - requirements[:controller] ||= Regexp.union(*possible_names) + requirements[:controller] ||= @set.controller_constraints if defaults[:controller] defaults[:action] ||= 'index' @@ -176,7 +175,7 @@ module ActionDispatch optional = false elsif segment =~ /^:(\w+)$/ if defaults.has_key?($1.to_sym) - defaults.delete($1.to_sym) + defaults.delete($1.to_sym) if defaults[$1.to_sym].nil? else optional = false end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6e112c9b54..e655d6a708 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,153 +1,247 @@ module ActionDispatch module Routing class Mapper - module Resources - def resource(*resources, &block) - options = resources.last.is_a?(Hash) ? resources.pop : {} - - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resource(r, options) } - return self + class Constraints + def self.new(app, constraints = []) + if constraints.any? + super(app, constraints) + else + app end + end - resource = resources.pop + def initialize(app, constraints = []) + @app, @constraints = app, constraints + end - if @scope[:scope_level] == :resources - member do - resource(resource, options, &block) + def call(env) + req = Rack::Request.new(env) + + @constraints.each { |constraint| + if constraint.respond_to?(:matches?) && !constraint.matches?(req) + return [ 404, {'X-Cascade' => 'pass'}, [] ] + elsif constraint.respond_to?(:call) && !constraint.call(req) + return [ 404, {'X-Cascade' => 'pass'}, [] ] end - return self - end + } + + @app.call(env) + end + end - singular = resource.to_s - plural = singular.pluralize + class Mapping + def initialize(set, scope, args) + @set, @scope = set, scope + @path, @options = extract_path_and_options(args) + end - controller(plural) do - namespace(resource) do - with_scope_level(:resource) do - yield if block_given? + def to_route + [ app, conditions, requirements, defaults, @options[:as] ] + end - get "", :to => :show, :as => "#{singular}" - post "", :to => :create - put "", :to => :update - delete "", :to => :destroy - get "new", :to => :new, :as => "new_#{singular}" - get "edit", :to => :edit, :as => "edit_#{singular}" - end + private + def extract_path_and_options(args) + options = args.extract_options! + + if args.empty? + path, to = options.find { |name, value| name.is_a?(String) } + options.merge!(:to => to).delete(path) if path + else + path = args.first end + + [ normalize_path(path), options ] end - self - end + def normalize_path(path) + path = nil if path == "" + path = "#{@scope[:path]}#{path}" if @scope[:path] + path = Rack::Mount::Utils.normalize_path(path) if path - def resources(*resources, &block) - options = resources.last.is_a?(Hash) ? resources.pop : {} + raise ArgumentError, "path is required" unless path - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resources(r, options) } - return self + path end - resource = resources.pop - plural = resource.to_s - singular = plural.singularize + def app + Constraints.new( + to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults), + blocks + ) + end - if @scope[:scope_level] == :resources - parent_resource = @scope[:scope_level_options][:name] - with_scope_level(:member) do - scope(":#{parent_resource}_id", :name_prefix => parent_resource) do - resources(resource, options, &block) - end - end - return self + def conditions + { :path_info => @path }.merge(constraints).merge(request_method_condition) end - if @scope[:options] && (prefix = @scope[:options][:name_prefix]) - plural = "#{prefix}_#{plural}" - singular = "#{prefix}_#{singular}" + def requirements + @requirements ||= returning(@options[:constraints] || {}) 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 - controller(resource) do - namespace(resource) do - with_scope_level(:resources, :name => singular) do - yield if block_given? + def defaults + @defaults ||= if to.respond_to?(:call) + { } + else + defaults = case to + when String + controller, action = to.split('#') + { :controller => controller, :action => action } + when Symbol + { :action => to.to_s }.merge(default_controller ? { :controller => default_controller } : {}) + else + default_controller ? { :controller => default_controller } : {} + end - member do - get "", :to => :show, :as => singular - put "", :to => :update - delete "", :to => :destroy - get "edit", :to => :edit, :as => "edit_#{singular}" - end + if defaults[:controller].blank? && segment_keys.exclude?("controller") + raise ArgumentError, "missing :controller" + end - collection do - get "", :to => :index, :as => plural - post "", :to => :create - get "new", :to => :new, :as => "new_#{singular}" - end + if defaults[:action].blank? && segment_keys.exclude?("action") + raise ArgumentError, "missing :action" end + + defaults end end - self - end - def collection - unless @scope[:scope_level] == :resources - raise ArgumentError, "can't use collection outside resources scope" - end + def blocks + if @options[:constraints].present? && !@options[:constraints].is_a?(Hash) + block = @options[:constraints] + else + block = nil + end - with_scope_level(:collection) do - yield + ((@scope[:blocks] || []) + [ block ]).compact end - end - def member - unless @scope[:scope_level] == :resources - raise ArgumentError, "can't use member outside resources scope" + def constraints + @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller } end - with_scope_level(:member) do - scope(":id") do - yield + def request_method_condition + if via = @options[:via] + via = Array(via).map { |m| m.to_s.upcase } + { :request_method => Regexp.union(*via) } + else + { } end end + + def segment_keys + @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new( + Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS) + ).names + end + + def to + @options[:to] + end + + def default_controller + @scope[:controller].to_s if @scope[:controller] + end + end + + module Base + def initialize(set) + @set = set + end + + def root(options = {}) + match '/', options.reverse_merge(:as => :root) end def match(*args) + @set.add_route(*Mapping.new(@set, @scope, args).to_route) + self + end + end + + module HttpHelpers + def get(*args, &block) + map_method(:get, *args, &block) + end + + def post(*args, &block) + map_method(:post, *args, &block) + end + + def put(*args, &block) + map_method(:put, *args, &block) + end + + def delete(*args, &block) + map_method(:delete, *args, &block) + end + + def redirect(*args, &block) options = args.last.is_a?(Hash) ? args.pop : {} - args.push(options) - case options.delete(:on) - when :collection - return collection { match(*args) } - when :member - return member { match(*args) } - end + path = args.shift || block + path_proc = path.is_a?(Proc) ? path : proc { |params| path % params } + status = options[:status] || 301 - if @scope[:scope_level] == :resources - raise ArgumentError, "can't define route directly in resources scope" - end + lambda do |env| + req = Rack::Request.new(env) + params = path_proc.call(env["action_dispatch.request.path_parameters"]) + url = req.scheme + '://' + req.host + params - super + [ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ] + end end private - def with_scope_level(kind, options = {}) - old, @scope[:scope_level] = @scope[:scope_level], kind - old_options, @scope[:scope_level_options] = @scope[:scope_level_options], options - yield - ensure - @scope[:scope_level] = old - @scope[:scope_level_options] = old_options + def map_method(method, *args, &block) + options = args.extract_options! + options[:via] = method + args.push(options) + match(*args, &block) + self end end module Scoping + def initialize(*args) + @scope = {} + super + end + def scope(*args) - options = args.last.is_a?(Hash) ? args.pop : {} + options = args.extract_options! + + case args.first + when String + options[:path] = args.first + when Symbol + options[:controller] = args.first + end + + if path = options.delete(:path) + path_set = true + path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s) + else + path_set = false + end + + if name_prefix = options.delete(:name_prefix) + name_prefix_set = true + name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix) + else + name_prefix_set = false + end + + if controller = options.delete(:controller) + controller_set = true + controller, @scope[:controller] = @scope[:controller], controller + else + controller_set = false + end constraints = options.delete(:constraints) || {} unless constraints.is_a?(Hash) @@ -158,27 +252,15 @@ module ActionDispatch options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) - path_set = controller_set = false - - case args.first - when String - path_set = true - path = args.first - path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}" - when Symbol - controller_set = true - controller = args.first - controller, @scope[:controller] = @scope[:controller], controller - end - yield self ensure - @scope[:path] = path if path_set - @scope[:controller] = controller if controller_set - @scope[:options] = options - @scope[:blocks] = blocks + @scope[:path] = path if path_set + @scope[:name_prefix] = name_prefix if name_prefix_set + @scope[:controller] = controller if controller_set + @scope[:options] = options + @scope[:blocks] = blocks @scope[:constraints] = constraints end @@ -187,151 +269,255 @@ module ActionDispatch end def namespace(path) - scope(path.to_s) { yield } + scope("/#{path}") { yield } end def constraints(constraints = {}) scope(:constraints => constraints) { yield } end - end - class Constraints - def initialize(app, constraints = []) - @app, @constraints = app, constraints - end + def match(*args) + options = args.extract_options! - def call(env) - req = Rack::Request.new(env) + options = (@scope[:options] || {}).merge(options) - @constraints.each { |constraint| - if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return [417, {}, []] - elsif constraint.respond_to?(:call) && !constraint.call(req) - return [417, {}, []] - end - } + if @scope[:name_prefix] && !options[:as].blank? + options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" + elsif @scope[:name_prefix] && options[:as] == "" + options[:as] = @scope[:name_prefix].to_s + end - @app.call(env) + args.push(options) + super(*args) end end - def initialize(set) - @set = set - @scope = {} - - extend Scoping - extend Resources - end + module Resources + class Resource #:nodoc: + attr_reader :plural, :singular - def get(*args, &block) - map_method(:get, *args, &block) - end + def initialize(entities, options = {}) + entities = entities.to_s - def post(*args, &block) - map_method(:post, *args, &block) - end + @plural = entities.pluralize + @singular = entities.singularize + end - def put(*args, &block) - map_method(:put, *args, &block) - end + def name + plural + end - def delete(*args, &block) - map_method(:delete, *args, &block) - end + def controller + plural + end - def root(options = {}) - match '/', options.merge(:as => :root) - end + def member_name + singular + end - def match(*args) - options = args.last.is_a?(Hash) ? args.pop : {} + def collection_name + plural + end - if args.length > 1 - args.each { |path| match(path, options) } - return self + def id_segment + ":#{singular}_id" + end end - if args.first.is_a?(Symbol) - return match(args.first.to_s, options.merge(:to => args.first.to_sym)) + class SingletonResource < Resource #:nodoc: + def initialize(entity, options = {}) + super + end + + def name + singular + end end - path = args.first + def resource(*resources, &block) + options = resources.extract_options! + + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resource(r, options) } + return self + end + + resource = SingletonResource.new(resources.pop) - options = (@scope[:options] || {}).merge(options) - conditions, defaults = {}, {} + if @scope[:scope_level] == :resources + nested do + resource(resource.name, options, &block) + end + return self + end - path = nil if path == "" - path = Rack::Mount::Utils.normalize_path(path) if path - path = "#{@scope[:path]}#{path}" if @scope[:path] + scope(:path => "/#{resource.name}", :controller => resource.controller) do + with_scope_level(:resource, resource) do + yield if block_given? - raise ArgumentError, "path is required" unless path + get "(.:format)", :to => :show, :as => resource.member_name + post "(.:format)", :to => :create + put "(.:format)", :to => :update + delete "(.:format)", :to => :destroy + get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" + get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + end + end - constraints = options[:constraints] || {} - unless constraints.is_a?(Hash) - block, constraints = constraints, {} + self end - blocks = ((@scope[:blocks] || []) + [block]).compact - constraints = (@scope[:constraints] || {}).merge(constraints) - options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) } - conditions[:path_info] = path - requirements = constraints.dup + def resources(*resources, &block) + options = resources.extract_options! + + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resources(r, options) } + return self + end + + resource = Resource.new(resources.pop) + + if @scope[:scope_level] == :resources + nested do + resources(resource.name, options, &block) + end + return self + end + + scope(:path => "/#{resource.name}", :controller => resource.controller) do + with_scope_level(:resources, resource) do + yield if block_given? + + with_scope_level(:collection) do + get "(.:format)", :to => :index, :as => resource.collection_name + post "(.:format)", :to => :create + + with_exclusive_name_prefix :new do + get "/new(.:format)", :to => :new, :as => resource.singular + end + end + + with_scope_level(:member) do + scope("/:id") do + get "(.:format)", :to => :show, :as => resource.member_name + put "(.:format)", :to => :update + delete "(.:format)", :to => :destroy + + with_exclusive_name_prefix :edit do + get "/edit(.:format)", :to => :edit, :as => resource.singular + end + end + end + end + end + + self + end - path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS) - segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names - constraints.reject! { |k, v| segment_keys.include?(k.to_s) } - conditions.merge!(constraints) + def collection + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use collection outside resources scope" + end - if via = options[:via] - via = Array(via).map { |m| m.to_s.upcase } - conditions[:request_method] = Regexp.union(*via) + with_scope_level(:collection) do + scope(:name_prefix => parent_resource.collection_name, :as => "") do + yield + end + end end - defaults[:controller] = @scope[:controller].to_s if @scope[:controller] + def member + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use member outside resources scope" + end - if options[:to].respond_to?(:call) - app = options[:to] - defaults.delete(:controller) - defaults.delete(:action) - elsif options[:to].is_a?(String) - defaults[:controller], defaults[:action] = options[:to].split('#') - elsif options[:to].is_a?(Symbol) - defaults[:action] = options[:to].to_s + with_scope_level(:member) do + scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do + yield + end + end end - app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults) - if app.is_a?(Routing::RouteSet::Dispatcher) - unless defaults.include?(:controller) || segment_keys.include?("controller") - raise ArgumentError, "missing :controller" + def nested + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use nested outside resources scope" end - unless defaults.include?(:action) || segment_keys.include?("action") - raise ArgumentError, "missing :action" + + with_scope_level(:nested) do + scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do + yield + end end end - app = Constraints.new(app, blocks) if blocks.any? - @set.add_route(app, conditions, requirements, defaults, options[:as]) + def match(*args) + options = args.extract_options! - self - end + if args.length > 1 + args.each { |path| match(path, options) } + return self + end - def redirect(path, options = {}) - status = options[:status] || 301 - lambda { |env| - req = Rack::Request.new(env) - url = req.scheme + '://' + req.host + path - [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']] - } - end + if args.first.is_a?(Symbol) + with_exclusive_name_prefix(args.first) do + return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym)) + end + end - private - def map_method(method, *args, &block) - options = args.last.is_a?(Hash) ? args.pop : {} - options[:via] = method args.push(options) - match(*args, &block) - self + + case options.delete(:on) + when :collection + return collection { match(*args) } + when :member + return member { match(*args) } + end + + if @scope[:scope_level] == :resources + raise ArgumentError, "can't define route directly in resources scope" + end + + super end + + protected + def parent_resource + @scope[:scope_level_resource] + end + + private + def with_exclusive_name_prefix(prefix) + begin + old_name_prefix = @scope[:name_prefix] + + if !old_name_prefix.blank? + @scope[:name_prefix] = "#{prefix}_#{@scope[:name_prefix]}" + else + @scope[:name_prefix] = prefix.to_s + end + + yield + ensure + @scope[:name_prefix] = old_name_prefix + end + end + + def with_scope_level(kind, resource = parent_resource) + old, @scope[:scope_level] = @scope[:scope_level], kind + old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource + yield + ensure + @scope[:scope_level] = old + @scope[:scope_level_resource] = old_resource + end + end + + include Base + include HttpHelpers + include Scoping + include Resources end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c15aaceb5b..bd397432ce 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -5,7 +5,7 @@ module ActionDispatch module Routing class RouteSet #:nodoc: NotFound = lambda { |env| - raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect} with #{env.inspect}" + raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" } PARAMETERS_KEY = 'action_dispatch.request.path_parameters' @@ -18,31 +18,37 @@ module ActionDispatch def call(env) params = env[PARAMETERS_KEY] + prepare_params!(params) + + unless controller = controller(params) + return [404, {'X-Cascade' => 'pass'}, []] + end + + controller.action(params[:action]).call(env) + end + + def prepare_params!(params) merge_default_action!(params) split_glob_param!(params) if @glob_param + params.each do |key, value| if value.is_a?(String) value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding) params[key] = URI.unescape(value) end end + end - if env['action_controller.recognize'] - [200, {}, params] - else - controller = controller(params) - controller.action(params[:action]).call(env) + def controller(params) + if params && params.has_key?(:controller) + controller = "#{params[:controller].camelize}Controller" + ActiveSupport::Inflector.constantize(controller) end + rescue NameError + nil end private - def controller(params) - if params && params.has_key?(:controller) - controller = "#{params[:controller].camelize}Controller" - ActiveSupport::Inflector.constantize(controller) - end - end - def merge_default_action!(params) params[:action] ||= 'index' end @@ -197,26 +203,40 @@ module ActionDispatch end end - attr_accessor :routes, :named_routes, :configuration_files + attr_accessor :routes, :named_routes + attr_accessor :disable_clear_and_finalize def initialize - self.configuration_files = [] - self.routes = [] self.named_routes = NamedRouteCollection.new - clear! + @disable_clear_and_finalize = false end def draw(&block) - clear! - Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block) + clear! unless @disable_clear_and_finalize + + mapper = Mapper.new(self) + if block.arity == 1 + mapper.instance_exec(DeprecatedMapper.new(self), &block) + else + mapper.instance_exec(&block) + end + + finalize! unless @disable_clear_and_finalize + + nil + end + + def finalize! @set.add_route(NotFound) install_helpers @set.freeze end def clear! + # Clear the controller cache so we may discover new ones + @controller_constraints = nil routes.clear named_routes.clear @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY) @@ -231,63 +251,38 @@ module ActionDispatch routes.empty? end - def add_configuration_file(path) - self.configuration_files << path - end - - # Deprecated accessor - def configuration_file=(path) - add_configuration_file(path) - end - - # Deprecated accessor - def configuration_file - configuration_files - end - - def load! - Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones - load_routes! - end - - # reload! will always force a reload whereas load checks the timestamp first - alias reload! load! - - def reload - if configuration_files.any? && @routes_last_modified - if routes_changed_at == @routes_last_modified - return # routes didn't change, don't reload - else - @routes_last_modified = routes_changed_at - end - end + CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/ - load! - end - - def load_routes! - if configuration_files.any? - configuration_files.each { |config| load(config) } - @routes_last_modified = routes_changed_at - else - draw do |map| - map.connect ":controller/:action/:id" - end + def controller_constraints + @controller_constraints ||= begin + source = controller_namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" } + source << CONTROLLER_REGEXP.source + Regexp.compile(source.sort.reverse.join('|')) end end - def routes_changed_at - routes_changed_at = nil + def controller_namespaces + namespaces = Set.new - configuration_files.each do |config| - config_changed_at = File.stat(config).mtime + # Find any nested controllers already in memory + ActionController::Base.subclasses.each do |klass| + controller_name = klass.underscore + namespaces << controller_name.split('/')[0...-1].join('/') + end - if routes_changed_at.nil? || config_changed_at > routes_changed_at - routes_changed_at = config_changed_at + # TODO: Move this into Railties + if defined?(Rails.application) + # Find namespaces in controllers/ directory + Rails.application.config.controller_paths.each do |load_path| + load_path = File.expand_path(load_path) + Dir["#{load_path}/**/*_controller.rb"].collect do |path| + namespaces << File.dirname(path).sub(/#{load_path}\/?/, '') + end end end - routes_changed_at + namespaces.delete('') + namespaces end def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil) @@ -372,7 +367,8 @@ module ActionDispatch end recall[:action] = options.delete(:action) if options[:action] == 'index' - parameterize = lambda { |name, value| + opts = {} + opts[:parameterize] = lambda { |name, value| if name == :controller value elsif value.is_a?(Array) @@ -382,14 +378,23 @@ module ActionDispatch end } - path = @set.url(named_route, options, recall, :parameterize => parameterize) + unless result = @set.generate(:path_info, named_route, options, recall, opts) + raise ActionController::RoutingError, "No route matches #{options.inspect}" + end + + path, params = result + params.each do |k, v| + if v + params[k] = v + else + params.delete(k) + end + end + if path && method == :generate_extras - uri = URI(path) - extras = uri.query ? - Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } : - [] - [uri.path, extras] + [path, params.keys] elsif path + path << "?#{params.to_query}" if params.any? path else raise ActionController::RoutingError, "No route matches #{options.inspect}" @@ -400,37 +405,11 @@ module ActionDispatch def call(env) @set.call(env) - rescue ActionController::RoutingError => e - raise e if env['action_controller.rescue_error'] == false - - method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO'] - - # Route was not recognized. Try to find out why (maybe wrong verb). - allows = HTTP_METHODS.select { |verb| - begin - recognize_path(path, {:method => verb}, false) - rescue ActionController::RoutingError - nil - end - } - - if !HTTP_METHODS.include?(method) - raise ActionController::NotImplemented.new(*allows) - elsif !allows.empty? - raise ActionController::MethodNotAllowed.new(*allows) - else - raise e - end - end - - def recognize(request) - params = recognize_path(request.path, extract_request_environment(request)) - request.path_parameters = params.with_indifferent_access - "#{params[:controller].to_s.camelize}Controller".constantize end - def recognize_path(path, environment = {}, rescue_error = true) + def recognize_path(path, environment = {}) method = (environment[:method] || "GET").to_s.upcase + path = Rack::Mount::Utils.normalize_path(path) begin env = Rack::MockRequest.env_for(path, {:method => method}) @@ -438,16 +417,16 @@ module ActionDispatch raise ActionController::RoutingError, e.message end - env['action_controller.recognize'] = true - env['action_controller.rescue_error'] = rescue_error - status, headers, body = call(env) - body - end + req = Rack::Request.new(env) + @set.recognize(req) do |route, params| + dispatcher = route.app + if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params) + dispatcher.prepare_params!(params) + return params + end + end - # Subclasses and plugins may override this method to extract further attributes - # from the request, for use by route conditions and such. - def extract_request_environment(request) - { :method => request.method } + raise ActionController::RoutingError, "No route matches #{path.inspect}" end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb index 9a917f704a..9c215de743 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb @@ -1,3 +1,5 @@ +require 'action_controller/vendor/html-scanner' + module ActionDispatch module Assertions module DomAssertions @@ -15,7 +17,7 @@ module ActionDispatch assert_block(full_message) { expected_dom == actual_dom } end - + # The negated form of +assert_dom_equivalent+. # # ==== Examples diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 501a7c4dfb..5686bbdbde 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -28,7 +28,7 @@ module ActionDispatch assert_block("") { true } # to count the assertion elsif type.is_a?(Fixnum) && @response.response_code == type assert_block("") { true } # to count the assertion - elsif type.is_a?(Symbol) && @response.response_code == ActionDispatch::StatusCodes::SYMBOL_TO_STATUS_CODE[type] + elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false } diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index e6d6b5a3ef..fc477afb17 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -46,7 +46,6 @@ module ActionDispatch request_method = nil end - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? request = recognized_request_for(path, request_method) expected_options = expected_options.clone @@ -80,7 +79,6 @@ module ActionDispatch def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) expected_path = "/#{expected_path}" unless expected_path[0] == ?/ # Load routes.rb if it hasn't been loaded. - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults) found_extras = options.reject {|k, v| ! extra_keys.include? k} @@ -126,6 +124,46 @@ module ActionDispatch assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message) end + # A helper to make it easier to test different route configurations. + # This method temporarily replaces ActionController::Routing::Routes + # with a new RouteSet instance. + # + # The new instance is yielded to the passed block. Typically the block + # will create some routes using <tt>map.draw { map.connect ... }</tt>: + # + # with_routing do |set| + # set.draw do |map| + # map.connect ':controller/:action/:id' + # assert_equal( + # ['/content/10/show', {}], + # map.generate(:controller => 'content', :id => 10, :action => 'show') + # end + # end + # 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 + 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 + end + + def method_missing(selector, *args, &block) + if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector) + @controller.send(selector, *args, &block) + else + super + end + end + private # Recognizes the route for a given path. def recognized_request_for(path, request_method = nil) @@ -134,9 +172,11 @@ module ActionDispatch # Assume given controller request = ActionController::TestRequest.new request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method - request.path = path + request.path = path + + params = ActionController::Routing::Routes.recognize_path(path, { :method => request.method }) + request.path_parameters = params.with_indifferent_access - ActionController::Routing::Routes.recognize(request) request end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index d22adfa749..c2dc591ff7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -1,3 +1,5 @@ +require 'action_controller/vendor/html-scanner' + #-- # Copyright (c) 2006 Assaf Arkin (http://labnotes.org) # Under MIT and/or CC By license. @@ -16,7 +18,7 @@ module ActionDispatch # # Use +css_select+ to select elements without making an assertions, either # from the response HTML or elements selected by the enclosing assertion. - # + # # In addition to HTML responses, you can make the following assertions: # * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations. # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions. @@ -53,8 +55,8 @@ module ActionDispatch # end # # # Selects all list items in unordered lists - # items = css_select("ul>li") - # + # items = css_select("ul>li") + # # # Selects all form tags and then all inputs inside the form # forms = css_select("form") # forms.each do |form| @@ -212,7 +214,7 @@ module ActionDispatch # Otherwise just operate on the response document. root = response_from_page_or_rjs end - + # First or second argument is the selector: string and we pass # all remaining arguments. Array and we pass the argument. Also # accepts selector itself. @@ -225,7 +227,7 @@ module ActionDispatch selector = arg else raise ArgumentError, "Expecting a selector as the first argument" end - + # Next argument is used for equality tests. equals = {} case arg = args.shift @@ -315,10 +317,10 @@ module ActionDispatch # Returns all matches elements. matches end - + def count_description(min, max) #:nodoc: pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')} - + if min && max && (max != min) "between #{min} and #{max} elements" elsif min && !(min == 1 && max == 1) @@ -327,7 +329,7 @@ module ActionDispatch "at most #{max} #{pluralize['element', max]}" end end - + # :call-seq: # assert_select_rjs(id?) { |elements| ... } # assert_select_rjs(statement, id?) { |elements| ... } @@ -344,7 +346,7 @@ module ActionDispatch # that update or insert an element with that identifier. # # Use the first argument to narrow down assertions to only statements - # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>, + # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>, # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>, # <tt>:insert_html</tt> and <tt>:redirect</tt>. # @@ -494,7 +496,7 @@ module ActionDispatch # end # end # end - # + # # # # Selects all paragraph tags from within the description of an RSS feed # assert_select_feed :rss, 2.0 do diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb index ef6867576e..5c735e61b2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb @@ -1,3 +1,5 @@ +require 'action_controller/vendor/html-scanner' + module ActionDispatch module Assertions # Pair of assertions to testing elements in the HTML output of the response. @@ -76,10 +78,10 @@ module ActionDispatch # # Assert that there is a "span" containing between 2 and 4 "em" tags # # as immediate children # assert_tag :tag => "span", - # :children => { :count => 2..4, :only => { :tag => "em" } } + # :children => { :count => 2..4, :only => { :tag => "em" } } # # # Get funky: assert that there is a "div", with an "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and containing a + # # and an "li" parent (with "class" = "enum"), and containing a # # "span" descendant that contains text matching /hello world/ # assert_tag :tag => "div", # :ancestor => { :tag => "ul" }, @@ -98,7 +100,7 @@ module ActionDispatch tag = find_tag(opts) assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}" end - + # Identical to +assert_tag+, but asserts that a matching tag does _not_ # exist. (See +assert_tag+ for a full discussion of the syntax.) # @@ -118,6 +120,19 @@ module ActionDispatch tag = find_tag(opts) assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}" end + + def find_tag(conditions) + html_document.find(conditions) + end + + def find_all_tag(conditions) + html_document.find_all(conditions) + end + + def html_document + xml = @response.content_type =~ /xml$/ + @html_document ||= HTML::Document.new(@response.body, false, xml) + end end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 40d6f97b2a..2a5f5dcd5c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -2,9 +2,7 @@ require 'stringio' require 'uri' require 'active_support/test_case' require 'active_support/core_ext/object/metaclass' - -# TODO: Remove circular dependency on ActionController -require 'action_controller/testing/process' +require 'rack/test' module ActionDispatch module Integration #:nodoc: @@ -128,9 +126,7 @@ module ActionDispatch DEFAULT_HOST = "www.example.com" include Test::Unit::Assertions - include ActionDispatch::Assertions - include ActionController::TestProcess - include RequestHelpers + include TestProcess, RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| delegate method, :to => :response, :allow_nil => true @@ -415,7 +411,7 @@ module ActionDispatch # At its simplest, you simply extend IntegrationTest and write your tests # using the get/post methods: # - # require "#{File.dirname(__FILE__)}/test_helper" + # require "test_helper" # # class ExampleTest < ActionController::IntegrationTest # fixtures :people @@ -439,7 +435,7 @@ module ActionDispatch # powerful testing DSL that is specific for your application. You can even # reference any named routes you happen to have defined! # - # require "#{File.dirname(__FILE__)}/test_helper" + # require "test_helper" # # class AdvancedTest < ActionController::IntegrationTest # fixtures :people, :rooms diff --git a/actionpack/lib/action_dispatch/testing/performance_test.rb b/actionpack/lib/action_dispatch/testing/performance_test.rb index b1ed9d31f4..1b9a6c18b7 100644 --- a/actionpack/lib/action_dispatch/testing/performance_test.rb +++ b/actionpack/lib/action_dispatch/testing/performance_test.rb @@ -1,15 +1,17 @@ require 'active_support/testing/performance' require 'active_support/testing/default' -module ActionDispatch - # An integration test that runs a code profiler on your test methods. - # Profiling output for combinations of each test method, measurement, and - # output format are written to your tmp/performance directory. - # - # By default, process_time is measured and both flat and graph_html output - # formats are written, so you'll have two output files per test method. - class PerformanceTest < ActionDispatch::IntegrationTest - include ActiveSupport::Testing::Performance - include ActiveSupport::Testing::Default +if defined?(ActiveSupport::Testing::Performance) + module ActionDispatch + # An integration test that runs a code profiler on your test methods. + # Profiling output for combinations of each test method, measurement, and + # output format are written to your tmp/performance directory. + # + # By default, process_time is measured and both flat and graph_html output + # formats are written, so you'll have two output files per test method. + class PerformanceTest < ActionDispatch::IntegrationTest + include ActiveSupport::Testing::Performance + include ActiveSupport::Testing::Default + end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb new file mode 100644 index 0000000000..eae703e1b6 --- /dev/null +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -0,0 +1,42 @@ +module ActionDispatch + module TestProcess + def assigns(key = nil) + assigns = {} + @controller.instance_variable_names.each do |ivar| + next if ActionController::Base.protected_instance_variables.include?(ivar) + assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar) + end + + key.nil? ? assigns : assigns[key.to_s] + end + + def session + @request.session + end + + def flash + @request.flash + end + + def cookies + @request.cookies.merge(@response.cookies) + end + + def redirect_to_url + @response.redirect_url + end + + # Shortcut for <tt>ARack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>: + # + # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') + # + # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter. + # This will not affect other platforms: + # + # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) + def fixture_file_upload(path, mime_type = nil, binary = false) + fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path) + Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary) + end + end +end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index e95e84aeb5..8ce6e82524 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -21,38 +21,40 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require File.join(File.dirname(__FILE__), "action_pack") +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +require 'active_support/ruby/shim' +require 'active_support/core_ext/class/attribute_accessors' + +require 'action_pack' module ActionView - def self.load_all! - [Context, Base, InlineTemplate, TemplateError] - end + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Context + autoload :Template + autoload :Helpers + autoload :SafeBuffer + + autoload_under "render" do + autoload :Partials + autoload :Rendering + end - autoload :Base, 'action_view/base' - autoload :Context, 'action_view/context' - autoload :Helpers, 'action_view/helpers' - autoload :MissingTemplate, 'action_view/base' - autoload :Partials, 'action_view/render/partials' - autoload :Resolver, 'action_view/template/resolver' - autoload :PathResolver, 'action_view/template/resolver' - autoload :PathSet, 'action_view/paths' - autoload :Rendering, 'action_view/render/rendering' - autoload :Template, 'action_view/template/template' - autoload :TemplateError, 'action_view/template/error' - autoload :TemplateHandler, 'action_view/template/handler' - autoload :TemplateHandlers, 'action_view/template/handlers' - autoload :TextTemplate, 'action_view/template/text' - autoload :Helpers, 'action_view/helpers' - autoload :FileSystemResolverWithFallback, 'action_view/template/resolver' - autoload :SafeBuffer, 'action_view/safe_buffer' + 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 :TemplateError, 'action_view/template/error' + autoload :TemplateHandler, 'action_view/template' + autoload :TemplateHandlers, 'action_view/template' + end end require 'action_view/erb/util' - +require 'action_view/base' I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" - -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift activesupport_path if File.directory?(activesupport_path) -require 'active_support' -require 'active_support/core_ext/class/attribute_accessors' diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index c33695770f..d69e5109fa 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -196,7 +196,7 @@ module ActionView #:nodoc: end class << self - delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' + delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' delegate :logger, :to => 'ActionController::Base', :allow_nil => true end diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 69d0d0fb67..f03ffe5ef4 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,3 +1,4 @@ +require 'action_controller/vendor/html-scanner' require 'action_view/helpers/tag_helper' module ActionView diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 564f12c955..35c431d78d 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -12,7 +12,7 @@ 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) + I18n.translate(scope_key_by_partial(key), options).html_safe! rescue I18n::MissingTranslationData => e keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope]) content_tag('span', keys.join(', '), :class => 'translation_missing') diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 23bde61f9c..0059b79e5f 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -4,7 +4,7 @@ module ActionView #:nodoc: # TODO: Clean this up if obj.is_a?(String) if cache.nil? - cache = !defined?(Rails) || Rails.application.config.cache_classes + cache = !defined?(Rails.application) || Rails.application.config.cache_classes end FileSystemResolverWithFallback.new(obj, :cache => cache) else diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 2eb88ae3e5..aeaf1ee4ff 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -181,20 +181,20 @@ module ActionView 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 - + def setup(options, block) partial = options[:partial] - + @options = options @locals = options[:locals] || {} @block = block - + if String === partial @object = options[:object] @path = partial @@ -240,7 +240,7 @@ module ActionView segments << template.render(@view, locals) end - + @template = template segments end @@ -294,7 +294,7 @@ module ActionView path && @templates[path] ||= _find_template(path) end end - + def _find_template(path) if controller = @view.controller prefix = controller.controller_path unless path.include?(?/) @@ -319,7 +319,7 @@ module ActionView _evaluate_assigns_and_ivars details = options[:_details] - + # Is this needed self.formats = details[:formats] if details renderer = PartialRenderer.new(self, options, nil) @@ -329,12 +329,12 @@ module ActionView end def _render_partial(options, &block) #:nodoc: - if @renderer + if defined? @renderer @renderer.setup(options, block) else @renderer = PartialRenderer.new(self, options, block) end - + @renderer.render end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 7006a5b968..d4d16b4d98 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -78,12 +78,12 @@ module ActionView end def _render_inline(inline, layout, options) - handler = Template.handler_class_for_extension(options[:type] || "erb") - template = Template.new(options[:inline], - "inline #{options[:inline].inspect}", handler, {}) + handler = Template.handler_class_for_extension(options[:type] || "erb") + template = Template.new(options[:inline], "inline template", handler, {}) - locals = options[:locals] + locals = options[:locals] content = template.render(self, locals) + _render_text(content, layout, locals) end @@ -91,6 +91,7 @@ module ActionView content = layout.render(self, locals) do |*name| _layout_for(*name) { content } end if layout + content end @@ -113,21 +114,16 @@ module ActionView msg end - locals = options[:locals] || {} - - content = if partial - _render_partial_object(template, options) - else - template.render(self, locals) - end - + locals = options[:locals] || {} + content = partial ? _render_partial_object(template, options) : template.render(self, locals) @_content_for[:layout] = content if layout @_layout = layout.identifier logger.info("Rendering template within #{layout.inspect}") if logger - content = layout.render(self, locals) {|*name| _layout_for(*name) } + content = layout.render(self, locals) { |*name| _layout_for(*name) } end + content end end diff --git a/actionpack/lib/action_view/safe_buffer.rb b/actionpack/lib/action_view/safe_buffer.rb index 09f44ab26f..6be05b9e1e 100644 --- a/actionpack/lib/action_view/safe_buffer.rb +++ b/actionpack/lib/action_view/safe_buffer.rb @@ -1,4 +1,3 @@ - module ActionView #:nodoc: class SafeBuffer < String def <<(value) diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template.rb index d1970ca3c7..d46c989d11 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -6,7 +6,16 @@ require "action_view/template/resolver" module ActionView class Template - extend TemplateHandlers + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Error + autoload :Handler + autoload :Handlers + autoload :Text + end + + extend Template::Handlers attr_reader :source, :identifier, :handler, :mime_type, :formats, :details def initialize(source, identifier, handler, details) @@ -32,11 +41,11 @@ module ActionView view.send(method_name, locals, &block) end rescue Exception => e - if e.is_a?(TemplateError) + if e.is_a?(Template::Error) e.sub_template_of(self) raise e else - raise TemplateError.new(self, view.assigns, e) + raise Template::Error.new(self, view.assigns, e) end end @@ -103,23 +112,8 @@ module ActionView logger.debug "Backtrace: #{e.backtrace.join("\n")}" end - raise ActionView::TemplateError.new(self, {}, e) - end - end - - class LocalsKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } - - def self.get(*locals) - @hash_keys[*locals] ||= new(klass, format, locale) + raise ActionView::Template::Error.new(self, {}, e) end - - attr_accessor :hash - def initialize(klass, format, locale) - @hash = locals.hash - end - - alias_method :eql?, :equal? end def build_method_name(locals) diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index aa21606f76..648f708d3d 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -1,101 +1,105 @@ require "active_support/core_ext/enumerable" module ActionView - # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a - # bunch of intimate details and uses it to report a very precise exception message. - class TemplateError < ActionViewError #:nodoc: - SOURCE_CODE_RADIUS = 3 + class Template + # The Template::Error exception is raised when the compilation of the template fails. This exception then gathers a + # bunch of intimate details and uses it to report a very precise exception message. + class Error < ActionViewError #:nodoc: + SOURCE_CODE_RADIUS = 3 - attr_reader :original_exception + attr_reader :original_exception - def initialize(template, assigns, original_exception) - @template, @assigns, @original_exception = template, assigns.dup, original_exception - @backtrace = compute_backtrace - end + def initialize(template, assigns, original_exception) + @template, @assigns, @original_exception = template, assigns.dup, original_exception + @backtrace = compute_backtrace + end - def file_name - @template.identifier - end + def file_name + @template.identifier + end - def message - ActiveSupport::Deprecation.silence { original_exception.message } - end + def message + ActiveSupport::Deprecation.silence { original_exception.message } + end - def clean_backtrace - if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) - Rails.backtrace_cleaner.clean(original_exception.backtrace) - else - original_exception.backtrace + def clean_backtrace + if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) + Rails.backtrace_cleaner.clean(original_exception.backtrace) + else + original_exception.backtrace + end end - end - def sub_template_message - if @sub_templates - "Trace of template inclusion: " + - @sub_templates.collect { |template| template.inspect }.join(", ") - else - "" + def sub_template_message + if @sub_templates + "Trace of template inclusion: " + + @sub_templates.collect { |template| template.inspect }.join(", ") + else + "" + end end - end - def source_extract(indentation = 0) - return unless num = line_number - num = num.to_i + def source_extract(indentation = 0) + return unless num = line_number + num = num.to_i - source_code = @template.source.split("\n") + source_code = @template.source.split("\n") - start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max - end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min + start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max + end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min - indent = ' ' * indentation - line_counter = start_on_line - return unless source_code = source_code[start_on_line..end_on_line] + indent = ' ' * indentation + line_counter = start_on_line + return unless source_code = source_code[start_on_line..end_on_line] - source_code.sum do |line| - line_counter += 1 - "#{indent}#{line_counter}: #{line}\n" + source_code.sum do |line| + line_counter += 1 + "#{indent}#{line_counter}: #{line}\n" + end end - end - - def sub_template_of(template_path) - @sub_templates ||= [] - @sub_templates << template_path - end - - def line_number - @line_number ||= - if file_name - regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ - $1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp } - end - end + def sub_template_of(template_path) + @sub_templates ||= [] + @sub_templates << template_path + end - def to_s - "\n#{self.class} (#{message}) #{source_location}:\n" + - "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n" - end + def line_number + @line_number ||= + if file_name + regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ - # don't do anything nontrivial here. Any raised exception from here becomes fatal - # (and can't be rescued). - def backtrace - @backtrace - end + $1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp } + end + end - private - def compute_backtrace - [ - "#{source_location.capitalize}\n\n#{source_extract(4)}\n " + - clean_backtrace.join("\n ") - ] + def to_s + "\n#{self.class} (#{message}) #{source_location}:\n" + + "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n" end - def source_location - if line_number - "on line ##{line_number} of " - else - 'in ' - end + file_name + # don't do anything nontrivial here. Any raised exception from here becomes fatal + # (and can't be rescued). + def backtrace + @backtrace end + + private + def compute_backtrace + [ + "#{source_location.capitalize}\n\n#{source_extract(4)}\n " + + clean_backtrace.join("\n ") + ] + end + + def source_location + if line_number + "on line ##{line_number} of " + else + 'in ' + end + file_name + end + end end -end
\ No newline at end of file + + TemplateError = Template::Error +end diff --git a/actionpack/lib/action_view/template/handler.rb b/actionpack/lib/action_view/template/handler.rb index 4bf58b9fa8..5a46a27893 100644 --- a/actionpack/lib/action_view/template/handler.rb +++ b/actionpack/lib/action_view/template/handler.rb @@ -3,34 +3,39 @@ require "action_dispatch/http/mime_type" # Legacy TemplateHandler stub module ActionView - module TemplateHandlers #:nodoc: - module Compilable - def self.included(base) - base.extend(ClassMethods) - end + class Template + module Handlers #:nodoc: + module Compilable + def self.included(base) + base.extend(ClassMethods) + end - module ClassMethods - def call(template) - new.compile(template) + module ClassMethods + def call(template) + new.compile(template) + end end - end - def compile(template) - raise "Need to implement #{self.class.name}#compile(template)" - end + def compile(template) + raise "Need to implement #{self.class.name}#compile(template)" + end + end end - end - class TemplateHandler - extlib_inheritable_accessor :default_format - self.default_format = Mime::HTML + class Template::Handler + extlib_inheritable_accessor :default_format + self.default_format = Mime::HTML - def self.call(template) - raise "Need to implement #{self.class.name}#call(template)" - end + def self.call(template) + raise "Need to implement #{self.class.name}#call(template)" + end - def render(template, local_assigns) - raise "Need to implement #{self.class.name}#render(template, local_assigns)" + def render(template, local_assigns) + raise "Need to implement #{self.class.name}#render(template, local_assigns)" + end end end + + TemplateHandlers = Template::Handlers + TemplateHandler = Template::Handler end diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb index faf54b9fe5..35488c0391 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionpack/lib/action_view/template/handlers.rb @@ -1,52 +1,54 @@ module ActionView #:nodoc: - module TemplateHandlers #:nodoc: - autoload :ERB, 'action_view/template/handlers/erb' - autoload :RJS, 'action_view/template/handlers/rjs' - autoload :Builder, 'action_view/template/handlers/builder' - - def self.extended(base) - base.register_default_template_handler :erb, TemplateHandlers::ERB - base.register_template_handler :rjs, TemplateHandlers::RJS - base.register_template_handler :builder, TemplateHandlers::Builder - - # TODO: Depreciate old template extensions - base.register_template_handler :rhtml, TemplateHandlers::ERB - base.register_template_handler :rxml, TemplateHandlers::Builder - end - - @@template_handlers = {} - @@default_template_handlers = nil + class Template + module Handlers #:nodoc: + autoload :ERB, 'action_view/template/handlers/erb' + autoload :RJS, 'action_view/template/handlers/rjs' + autoload :Builder, 'action_view/template/handlers/builder' + + def self.extended(base) + base.register_default_template_handler :erb, ERB + base.register_template_handler :rjs, RJS + base.register_template_handler :builder, Builder + + # TODO: Depreciate old template extensions + base.register_template_handler :rhtml, ERB + base.register_template_handler :rxml, Builder + end + + @@template_handlers = {} + @@default_template_handlers = nil - def self.extensions - @@template_handlers.keys - end - - # Register a class that knows how to handle template files with the given - # extension. This can be used to implement new template types. - # The constructor for the class must take the ActiveView::Base instance - # as a parameter, and the class must implement a +render+ method that - # takes the contents of the template to render as well as the Hash of - # local assigns available to the template. The +render+ method ought to - # return the rendered template as a string. - def register_template_handler(extension, klass) - @@template_handlers[extension.to_sym] = klass - end - - def template_handler_extensions - @@template_handlers.keys.map {|key| key.to_s }.sort - end - - def registered_template_handler(extension) - extension && @@template_handlers[extension.to_sym] - end - - def register_default_template_handler(extension, klass) - register_template_handler(extension, klass) - @@default_template_handlers = klass - end - - def handler_class_for_extension(extension) - (extension && registered_template_handler(extension.to_sym)) || @@default_template_handlers + def self.extensions + @@template_handlers.keys + end + + # Register a class that knows how to handle template files with the given + # extension. This can be used to implement new template types. + # The constructor for the class must take the ActiveView::Base instance + # as a parameter, and the class must implement a +render+ method that + # takes the contents of the template to render as well as the Hash of + # local assigns available to the template. The +render+ method ought to + # return the rendered template as a string. + def register_template_handler(extension, klass) + @@template_handlers[extension.to_sym] = klass + end + + def template_handler_extensions + @@template_handlers.keys.map {|key| key.to_s }.sort + end + + def registered_template_handler(extension) + extension && @@template_handlers[extension.to_sym] + end + + def register_default_template_handler(extension, klass) + register_template_handler(extension, klass) + @@default_template_handlers = klass + end + + def handler_class_for_extension(extension) + (extension && registered_template_handler(extension.to_sym)) || @@default_template_handlers + end end end end diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb index 5f381f7bf0..a93cfca8aa 100644 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ b/actionpack/lib/action_view/template/handlers/builder.rb @@ -1,6 +1,6 @@ module ActionView - module TemplateHandlers - class Builder < TemplateHandler + module Template::Handlers + class Builder < Template::Handler include Compilable self.default_format = Mime::XML diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 88aeb4b053..93a4315108 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -3,14 +3,15 @@ require 'active_support/core_ext/string/output_safety' require 'erubis' module ActionView - module TemplateHandlers + module Template::Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) src << "@output_buffer = ActionView::SafeBuffer.new;" end def add_text(src, text) - src << "@output_buffer << ('" << escape_text(text) << "'.html_safe!);" + return if text.empty? + src << "@output_buffer.safe_concat('" << escape_text(text) << "');" end def add_expr_literal(src, code) @@ -26,7 +27,7 @@ module ActionView end end - class ERB < TemplateHandler + class ERB < Template::Handler include Compilable ## diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb index b1d15dc209..63e7dc0902 100644 --- a/actionpack/lib/action_view/template/handlers/rjs.rb +++ b/actionpack/lib/action_view/template/handlers/rjs.rb @@ -1,6 +1,6 @@ module ActionView - module TemplateHandlers - class RJS < TemplateHandler + module Template::Handlers + class RJS < Template::Handler include Compilable self.default_format = Mime::JS diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 7336114e1b..a2f4ab2ef5 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -1,6 +1,6 @@ require "pathname" require "active_support/core_ext/class" -require "action_view/template/template" +require "action_view/template" module ActionView # Abstract superclass @@ -20,7 +20,7 @@ module ActionView register_detail(:locale) { [I18n.locale] } register_detail(:formats) { Mime::SET.symbols } register_detail(:handlers, :allow_nil => false) do - TemplateHandlers.extensions + Template::Handlers.extensions end def initialize(options = {}) @@ -65,7 +65,7 @@ module ActionView # as well as incorrectly putting part of the path in the template # name instead of the prefix. def normalize_name(name, prefix) - handlers = TemplateHandlers.extensions.join('|') + handlers = Template::Handlers.extensions.join('|') name = name.to_s.gsub(/\.(?:#{handlers})$/, '') parts = name.split('/') diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index f6e011a5ab..67e086d8bd 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,38 +1,40 @@ module ActionView #:nodoc: - class TextTemplate < String #:nodoc: - HTML = Mime[:html] - - def initialize(string, content_type = HTML) - super(string.to_s) - @content_type = Mime[content_type] || content_type - end - - def details - {:formats => [@content_type.to_sym]} - end - - def identifier - self - end - - def inspect - 'text template' - end - - def render(*args) - to_s - end - - def mime_type - @content_type - end - - def formats - [mime_type] - end - - def partial? - false + class Template + class Text < String #:nodoc: + HTML = Mime[:html] + + def initialize(string, content_type = HTML) + super(string.to_s) + @content_type = Mime[content_type] || content_type + end + + def details + {:formats => [@content_type.to_sym]} + end + + def identifier + self + end + + def inspect + 'text template' + end + + def render(*args) + to_s + end + + def mime_type + @content_type + end + + def formats + [mime_type] + end + + def partial? + false + end end end end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 86bbad822d..be9a2ed50d 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,5 +1,5 @@ require 'active_support/test_case' -require 'action_controller/testing/test_case' +require 'action_controller/test_case' module ActionView class Base @@ -39,8 +39,7 @@ module ActionView end end - include ActionDispatch::Assertions - include ActionController::TestProcess + include ActionDispatch::Assertions, ActionDispatch::TestProcess include ActionView::Context include ActionController::PolymorphicRoutes diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 524381509d..4ad87d9762 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -28,7 +28,7 @@ module AbstractController # Test Render mixin # ==== class RenderingController < AbstractController::Base - include ::AbstractController::RenderingController + include ::AbstractController::Rendering def _prefix() end diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index efcd68e5c8..ade29140ba 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -6,7 +6,7 @@ module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include AbstractController::RenderingController + include AbstractController::Rendering include Helpers def with_module diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index ae2f1bf1f2..df73d948f0 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -6,7 +6,7 @@ module AbstractControllerTests # Base controller for these tests class Base < AbstractController::Base - include AbstractController::RenderingController + include AbstractController::Rendering include AbstractController::Layouts self.view_paths = [ActionView::FixtureResolver.new( @@ -23,7 +23,7 @@ module AbstractControllerTests self.view_paths = [] def index - render :_template => ActionView::TextTemplate.new("Hello blank!") + render :_template => ActionView::Template::Text.new("Hello blank!") end end @@ -31,19 +31,19 @@ module AbstractControllerTests layout "hello" def index - render :_template => ActionView::TextTemplate.new("Hello string!") + render :_template => ActionView::Template::Text.new("Hello string!") end def overwrite_default - render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => :default + render :_template => ActionView::Template::Text.new("Hello string!"), :layout => :default end def overwrite_false - render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => false + render :_template => ActionView::Template::Text.new("Hello string!"), :layout => false end def overwrite_string - render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => "omg" + render :_template => ActionView::Template::Text.new("Hello string!"), :layout => "omg" end def overwrite_skip @@ -72,7 +72,7 @@ module AbstractControllerTests layout :hello def index - render :_template => ActionView::TextTemplate.new("Hello symbol!") + render :_template => ActionView::Template::Text.new("Hello symbol!") end private def hello @@ -84,7 +84,7 @@ module AbstractControllerTests layout :no_hello def index - render :_template => ActionView::TextTemplate.new("Hello missing symbol!") + render :_template => ActionView::Template::Text.new("Hello missing symbol!") end private def no_hello @@ -96,7 +96,7 @@ module AbstractControllerTests layout :nilz def index - render :_template => ActionView::TextTemplate.new("Hello nilz!") + render :_template => ActionView::Template::Text.new("Hello nilz!") end def nilz() end @@ -106,7 +106,7 @@ module AbstractControllerTests layout :objekt def index - render :_template => ActionView::TextTemplate.new("Hello nilz!") + render :_template => ActionView::Template::Text.new("Hello nilz!") end def objekt @@ -118,7 +118,7 @@ module AbstractControllerTests layout :omg_no_method def index - render :_template => ActionView::TextTemplate.new("Hello boom!") + render :_template => ActionView::Template::Text.new("Hello boom!") end end @@ -126,7 +126,7 @@ module AbstractControllerTests layout "missing" def index - render :_template => ActionView::TextTemplate.new("Hello missing!") + render :_template => ActionView::Template::Text.new("Hello missing!") end end @@ -134,7 +134,7 @@ module AbstractControllerTests layout false def index - render :_template => ActionView::TextTemplate.new("Hello false!") + render :_template => ActionView::Template::Text.new("Hello false!") end end @@ -142,7 +142,7 @@ module AbstractControllerTests layout nil def index - render :_template => ActionView::TextTemplate.new("Hello nil!") + render :_template => ActionView::Template::Text.new("Hello nil!") end end diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb index 6f9bb693f7..8b0b0fff03 100644 --- a/actionpack/test/abstract/localized_cache_test.rb +++ b/actionpack/test/abstract/localized_cache_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class CachedController < AbstractController::Base - include AbstractController::RenderingController + include AbstractController::Rendering include AbstractController::LocalizedCache self.view_paths = [ActionView::FixtureResolver.new( diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb index 45a4763fe4..be0478b638 100644 --- a/actionpack/test/abstract/render_test.rb +++ b/actionpack/test/abstract/render_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class ControllerRenderer < AbstractController::Base - include AbstractController::RenderingController + include AbstractController::Rendering self.view_paths = [ActionView::FixtureResolver.new( "default.erb" => "With Default", @@ -38,7 +38,7 @@ module AbstractController end def object - render :_template => ActionView::TextTemplate.new("With Object") + render :_template => ActionView::Template::Text.new("With Object") end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 775cfc82bf..a9341b60df 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,12 +1,9 @@ -root = File.expand_path('../../..', __FILE__) begin - require "#{root}/vendor/gems/environment" + require File.expand_path('../../../vendor/gems/environment', __FILE__) rescue LoadError - $:.unshift "#{root}/activesupport/lib" - $:.unshift "#{root}/activemodel/lib" end -lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +lib = File.expand_path('../../lib', __FILE__) $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) $:.unshift(File.dirname(__FILE__) + '/lib') @@ -16,18 +13,20 @@ $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') require 'test/unit' -require 'active_support' -require 'active_support/test_case' require 'abstract_controller' require 'action_controller' require 'action_view' require 'action_view/base' require 'action_dispatch' -require 'active_model' require 'fixture_template' +require 'active_support/test_case' require 'action_view/test_case' require 'active_support/dependencies' +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) +require 'active_model' + begin require 'ruby-debug' Debugger.settings[:autoeval] = true @@ -83,7 +82,7 @@ class ActiveSupport::TestCase # have been loaded. setup_once do ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' + match ':controller(/:action(/:id))' end end end @@ -191,26 +190,14 @@ class ::ApplicationController < ActionController::Base end module ActionController - module Routing - def self.possible_controllers - @@possible_controllers ||= [] - end - end - class Base include ActionController::Testing - - def self.inherited(klass) - name = klass.name.underscore.sub(/_controller$/, '') - ActionController::Routing.possible_controllers << name unless name.blank? - super - end end Base.view_paths = FIXTURE_LOAD_PATH class TestCase - include TestProcess + include ActionDispatch::TestProcess def assert_template(options = {}, message = nil) validate_request! diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb index 9e0c66055d..9a094cf66b 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -11,19 +11,15 @@ class ActiveRecordTestConnector end # Try to grab AR -if defined?(ActiveRecord) && defined?(Fixtures) - $stderr.puts 'Active Record is already loaded, running tests' -else - $stderr.print 'Attempting to load Active Record... ' +unless defined?(ActiveRecord) && defined?(Fixtures) begin PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib" raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) $LOAD_PATH.unshift PATH_TO_AR require 'active_record' require 'active_record/fixtures' - $stderr.puts 'success' rescue LoadError => e - $stderr.print "failed. Skipping Active Record assertion tests: #{e}" + $stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}" ActiveRecordTestConnector.able_to_connect = false end end diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index c6c079f88c..61bee1b66c 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -155,7 +155,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "active_record_store_test/test" + match ':action', :to => 'active_record_store_test/test' end @app = ActiveRecord::SessionStore.new(set, options.reverse_merge(:key => '_session_id')) yield diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 901cb940ea..d54be9bdc0 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -221,8 +221,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirect_to_named_route with_routing do |set| set.draw do |map| - map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing' - map.connect ':controller/:action/:id' + match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one + match ':controller/:action' end set.install_helpers @@ -235,9 +235,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirect_to_named_route_failure with_routing do |set| set.draw do |map| - map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one' - map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' - map.connect ':controller/:action/:id' + match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one + match 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two + match ':controller/:action' end process :redirect_to_named_route assert_raise(ActiveSupport::TestCase::Assertion) do @@ -255,8 +255,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirect_to_nested_named_route with_routing do |set| set.draw do |map| - map.admin_inner_module 'admin/inner_module', :controller => 'admin/inner_module', :action => 'index' - map.connect ':controller/:action/:id' + match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module + match ':controller/:action' end @controller = Admin::InnerModuleController.new process :redirect_to_index @@ -268,8 +268,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirected_to_top_level_named_route_from_nested_controller with_routing do |set| set.draw do |map| - map.top_level '/action_pack_assertions/:id', :controller => 'action_pack_assertions', :action => 'index' - map.connect ':controller/:action/:id' + match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level + match ':controller/:action' end @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route @@ -282,8 +282,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase 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 - map.top_level '/user/:id', :controller => 'user', :action => 'index' - map.connect ':controller/:action/:id' + match '/user/:id', :to => 'user#index', :as => :top_level + match ':controller/:action' end @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index b57550a69a..8f8ada8d8c 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -179,8 +179,8 @@ class DefaultUrlOptionsTest < ActionController::TestCase def test_default_url_options_are_used_if_set with_routing do |set| set.draw do |map| - map.default_url_options 'default_url_options', :controller => 'default_url_options' - map.connect ':controller/:action/:id' + match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options + match ':controller/:action' end get :default_url_options_action # Make a dummy request so that the controller is initialized properly. @@ -210,7 +210,7 @@ class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase def test_named_routes_still_work with_routing do |set| set.draw do |map| - map.resources :things + resources :things end EmptyController.send :include, ActionController::UrlWriter diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 3ce90b6ccf..4ea2e57741 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -70,8 +70,8 @@ class PageCachingTest < ActionController::TestCase def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route with_routing do |set| set.draw do |map| - map.main '', :controller => 'posts', :format => nil - map.formatted_posts 'posts.:format', :controller => 'posts' + match 'posts.:format', :to => 'posts#index', :as => :formatted_posts + match '/', :to => 'posts#index', :as => :main end @params[:format] = 'rss' assert_equal '/posts.rss', @rewriter.rewrite(@params) @@ -422,8 +422,7 @@ class ActionCacheTest < ActionController::TestCase def test_xml_version_of_resource_is_treated_as_different_cache with_routing do |set| set.draw do |map| - map.connect ':controller/:action.:format' - map.connect ':controller/:action' + match ':controller(/:action(.:format))' end get :index, :format => 'xml' @@ -632,13 +631,16 @@ class FragmentCachingTest < ActionController::TestCase def test_fragment_for_logging fragment_computed = false - ActiveSupport::Notifications.queue.expects(:publish).times(2) + events = [] + ActiveSupport::Notifications.subscribe { |*args| events << args } buffer = 'generated till now -> ' @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } assert fragment_computed assert_equal 'generated till now -> ', buffer + ActiveSupport::Notifications.notifier.wait + assert_equal [:fragment_exist?, :write_fragment], events.map(&:first) end end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 53d4364576..84d5ce6ad4 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -1,5 +1,7 @@ require 'abstract_unit' +ActionController::Base.cookie_verifier_secret = "thisISverySECRET123" + class CookieTest < ActionController::TestCase class TestController < ActionController::Base def authenticate @@ -47,6 +49,21 @@ class CookieTest < ActionController::TestCase cookies["user_name"] = { :value => "david", :httponly => true } head :ok end + + def set_permanent_cookie + cookies.permanent[:user_name] = "Jamie" + head :ok + end + + def set_signed_cookie + cookies.signed[:user_id] = 45 + head :ok + end + + def set_permanent_signed_cookie + cookies.permanent.signed[:remember_me] = 100 + head :ok + end end tests TestController @@ -134,6 +151,24 @@ class CookieTest < ActionController::TestCase response = get :authenticate assert response.headers["Set-Cookie"] =~ /user_name=david/ end + + def test_permanent_cookie + get :set_permanent_cookie + assert_match /Jamie/, @response.headers["Set-Cookie"] + assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"] + end + + def test_signed_cookie + get :set_signed_cookie + assert_equal 45, @controller.send(:cookies).signed[:user_id] + end + + def test_permanent_signed_cookie + get :set_permanent_signed_cookie + assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"] + assert_equal 100, @controller.send(:cookies).signed[:remember_me] + end + private def assert_cookie_header(expected) diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index 622d67287d..64f1ad7610 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -15,7 +15,6 @@ class DispatcherTest < Test::Unit::TestCase ActionDispatch::Callbacks.reset_callbacks(:call) ActionController::Routing::Routes.stubs(:call).returns([200, {}, 'response']) - ActionController::Routing::Routes.stubs(:reload) Dispatcher.stubs(:require_dependency) end @@ -28,18 +27,6 @@ class DispatcherTest < Test::Unit::TestCase dispatch(false) end - def test_reloads_routes_before_dispatch_if_in_loading_mode - ActionController::Routing::Routes.expects(:reload).once - dispatch(false) - end - - def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode - ActionController::Routing::Routes.expects(:reload).never - ActiveSupport::Dependencies.expects(:clear).never - - dispatch - end - def test_prepare_callbacks a = b = c = nil ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 } diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index c448f36cb3..a9b60386f1 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -34,7 +34,7 @@ class FlashTest < ActionController::TestCase flash.keep render :inline => "hello" end - + def use_flash_and_update_it flash.update("this" => "hello again") @flash_copy = {}.update flash @@ -72,6 +72,18 @@ class FlashTest < ActionController::TestCase redirect_to :action => "std_action" @flash_copy = {}.update(flash) end + + def redirect_with_alert + redirect_to '/nowhere', :alert => "Beware the nowheres!" + end + + def redirect_with_notice + redirect_to '/somewhere', :notice => "Good luck in the somewheres!" + end + + def redirect_with_other_flashes + redirect_to '/wonderland', :flash => { :joyride => "Horses!" } + end end tests TestController @@ -89,7 +101,7 @@ class FlashTest < ActionController::TestCase def test_keep_flash get :set_flash - + get :use_flash_and_keep_it assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello", assigns["flashy"] @@ -100,7 +112,7 @@ class FlashTest < ActionController::TestCase get :use_flash assert_nil assigns["flash_copy"]["that"], "On third flash" end - + def test_flash_now get :set_flash_now assert_equal "hello", assigns["flash_copy"]["that"] @@ -111,8 +123,8 @@ class FlashTest < ActionController::TestCase assert_nil assigns["flash_copy"]["that"] assert_nil assigns["flash_copy"]["foo"] assert_nil assigns["flashy"] - end - + end + def test_update_flash get :set_flash get :use_flash_and_update_it @@ -128,7 +140,7 @@ class FlashTest < ActionController::TestCase assert_equal "hello", assigns["flashy_that"] assert_equal "good-bye", assigns["flashy_this"] assert_nil assigns["flashy_that_reset"] - end + end def test_does_not_set_the_session_if_the_flash_is_empty get :std_action @@ -153,11 +165,26 @@ class FlashTest < ActionController::TestCase assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed assert_nil flash.discard(:unknown) # non existant key passed assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard()) # nothing passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil)) # nothing passed + assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil)) # nothing passed assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed assert_nil flash.keep(:unknown) # non existant key passed assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep()) # nothing passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil)) # nothing passed + assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil)) # nothing passed + end + + def test_redirect_to_with_alert + get :redirect_with_alert + assert_equal "Beware the nowheres!", @controller.send(:flash)[:alert] + end + + def test_redirect_to_with_notice + get :redirect_with_notice + assert_equal "Good luck in the somewheres!", @controller.send(:flash)[:notice] + end + + def test_redirect_to_with_other_flashes + get :redirect_with_other_flashes + assert_equal "Horses!", @controller.send(:flash)[:joyride] end -end +end
\ No newline at end of file diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index b9be163904..9030e562bb 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -191,7 +191,7 @@ class IsolatedHelpersTest < Test::Unit::TestCase end def test_helper_in_a - assert_raise(ActionView::TemplateError) { call_controller(A, "index") } + assert_raise(ActionView::Template::Error) { call_controller(A, "index") } end def test_helper_in_b diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index feb2f81cc1..f635253156 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -115,7 +115,7 @@ class RendersNoLayoutController < LayoutTest end class LayoutSetInResponseTest < ActionController::TestCase - include ActionView::TemplateHandlers + include ActionView::Template::Handlers def test_layout_set_when_using_default_layout @controller = DefaultLayoutController.new diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index fee9cf46f9..6b9cace9cd 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -507,6 +507,13 @@ class RespondWithController < ActionController::Base end end + def using_responder_with_respond + responder = Class.new(ActionController::Responder) do + def respond; @controller.render :text => "respond #{format}"; end + end + respond_with(Customer.new("david", 13), :responder => responder) + end + protected def _render_js(js, options) @@ -592,14 +599,18 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_post_with_html + def test_using_resource_for_post_with_html_redirects_on_success with_test_route_set do post :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 @response.redirect? + end + end + def test_using_resource_for_post_with_html_rerender_on_failure + with_test_route_set do errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) post :using_resource @@ -610,16 +621,20 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_post_with_xml + def test_using_resource_for_post_with_xml_yields_created_on_success with_test_route_set do @request.accept = "application/xml" - post :using_resource assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "<name>david</name>", @response.body assert_equal "http://www.example.com/customers/13", @response.location + end + end + def test_using_resource_for_post_with_xml_yields_unprocessable_entity_on_failure + with_test_route_set do + @request.accept = "application/xml" errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) post :using_resource @@ -630,14 +645,18 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_put_with_html + def test_using_resource_for_put_with_html_redirects_on_success with_test_route_set do put :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 @response.redirect? + end + end + def test_using_resource_for_put_with_html_rerender_on_failure + with_test_route_set do errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) put :using_resource @@ -648,14 +667,16 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_put_with_xml + def test_using_resource_for_put_with_xml_yields_ok_on_success @request.accept = "application/xml" - put :using_resource assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body + end + def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure + @request.accept = "application/xml" errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) put :using_resource @@ -665,7 +686,7 @@ class RespondWithControllerTest < ActionController::TestCase assert_nil @response.location end - def test_using_resource_for_delete_with_html + def test_using_resource_for_delete_with_html_redirects_on_success with_test_route_set do Customer.any_instance.stubs(:destroyed?).returns(true) delete :using_resource @@ -675,7 +696,7 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_delete_with_xml + def test_using_resource_for_delete_with_xml_yields_ok_on_success Customer.any_instance.stubs(:destroyed?).returns(true) @request.accept = "application/xml" delete :using_resource @@ -684,6 +705,18 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal " ", @response.body end + def test_using_resource_for_delete_with_html_redirects_on_failure + with_test_route_set do + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:destroyed?).returns(false) + 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 + end + end + def test_using_resource_with_parent_for_get @request.accept = "application/xml" get :using_resource_with_parent @@ -735,6 +768,16 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "foo - #{[:html].to_s}", @controller.response_body end + def test_respond_as_responder_entry_point + @request.accept = "text/html" + get :using_responder_with_respond + assert_equal "respond html", @response.body + + @request.accept = "application/xml" + get :using_responder_with_respond + assert_equal "respond xml", @response.body + end + def test_clear_respond_to @controller = InheritedRespondWithController.new @request.accept = "text/html" @@ -810,9 +853,11 @@ class RespondWithControllerTest < ActionController::TestCase def with_test_route_set with_routing do |set| set.draw do |map| - map.resources :customers - map.resources :quiz_stores, :has_many => :customers - map.connect ":controller/:action/:id" + resources :customers + resources :quiz_stores do + resources :customers + end + match ":controller/:action" end yield end diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index ea278fd8f0..570ff4a41b 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -233,8 +233,8 @@ class RedirectTest < ActionController::TestCase def test_redirect_to_record with_routing do |set| set.draw do |map| - map.resources :workshops - map.connect ':controller/:action/:id' + resources :workshops + match ':controller/:action' end get :redirect_to_existing_record diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb index 51c3c55545..dfc4f2db8c 100644 --- a/actionpack/test/controller/render_other_test.rb +++ b/actionpack/test/controller/render_other_test.rb @@ -2,6 +2,11 @@ require 'abstract_unit' require 'controller/fake_models' require 'pathname' +ActionController.add_renderer :simon do |says, options| + self.content_type = Mime::TEXT + self.response_body = "Simon says: #{says}" +end + class RenderOtherTest < ActionController::TestCase class TestController < ActionController::Base protect_from_forgery @@ -109,6 +114,10 @@ class RenderOtherTest < ActionController::TestCase end end + def render_simon_says + render :simon => "foo" + end + private def default_render if @alternate_default_render @@ -240,4 +249,9 @@ class RenderOtherTest < ActionController::TestCase xhr :get, :render_alternate_default assert_equal %(Element.replace("foo", "partial html");), @response.body end + + def test_using_custom_render_option + get :render_simon_says + assert_equal "Simon says: foo", @response.body + end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index b32325fa20..54f2739d38 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -39,35 +39,35 @@ class TestController < ActionController::Base render :action => 'hello_world' end end - + def conditional_hello_with_public_header if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) render :action => 'hello_world' end end - + def conditional_hello_with_public_header_and_expires_at expires_in 1.minute if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) render :action => 'hello_world' end end - + def conditional_hello_with_expires_in - expires_in 1.minute + expires_in 60.1.seconds render :action => 'hello_world' end - + def conditional_hello_with_expires_in_with_public expires_in 1.minute, :public => true render :action => 'hello_world' end - + def conditional_hello_with_expires_in_with_public_with_more_keys expires_in 1.minute, :public => true, 'max-stale' => 5.hours render :action => 'hello_world' end - + def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours render :action => 'hello_world' @@ -272,7 +272,7 @@ class TestController < ActionController::Base def builder_layout_test render :action => "hello", :layout => "layouts/builder" end - + # :move: test this in ActionView def builder_partial_test render :action => "hello_world_container" @@ -1093,8 +1093,8 @@ class RenderTest < ActionController::TestCase def test_head_with_location_object with_routing do |set| set.draw do |map| - map.resources :customers - map.connect ':controller/:action/:id' + resources :customers + match ':controller/:action' end get :head_with_location_object @@ -1125,7 +1125,7 @@ class RenderTest < ActionController::TestCase assert !@response.headers.include?('Content-Length') assert_response :no_content - ActionDispatch::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code| + Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| get :head_with_symbolic_status, :status => status.to_s assert_equal code, @response.response_code assert_response status @@ -1133,7 +1133,7 @@ class RenderTest < ActionController::TestCase end def test_head_with_integer_status - ActionDispatch::StatusCodes::STATUS_CODES.each do |code, message| + Rack::Utils::HTTP_STATUS_CODES.each do |code, message| get :head_with_integer_status, :status => code.to_s assert_equal message, @response.message end @@ -1306,22 +1306,22 @@ class ExpiresInRenderTest < ActionController::TestCase def setup @request.host = "www.nextangle.com" end - + def test_expires_in_header get :conditional_hello_with_expires_in assert_equal "max-age=60, private", @response.headers["Cache-Control"] end - + def test_expires_in_header_with_public get :conditional_hello_with_expires_in_with_public assert_equal "max-age=60, public", @response.headers["Cache-Control"] end - + def test_expires_in_header_with_additional_headers get :conditional_hello_with_expires_in_with_public_with_more_keys assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] end - + def test_expires_in_old_syntax get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] @@ -1425,12 +1425,12 @@ class EtagRenderTest < ActionController::TestCase get :conditional_hello_with_bangs assert_response :not_modified end - + def test_etag_with_public_true_should_set_header get :conditional_hello_with_public_header assert_equal "public", @response.headers['Cache-Control'] end - + def test_etag_with_public_true_should_set_header_and_retain_other_headers get :conditional_hello_with_public_header_and_expires_at assert_equal "max-age=60, public", @response.headers['Cache-Control'] diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 68a52c3e8c..b5b0d0b9d5 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -61,8 +61,8 @@ class RenderXmlTest < ActionController::TestCase def test_rendering_with_object_location_should_set_header_with_url_for with_routing do |set| set.draw do |map| - map.resources :customers - map.connect ':controller/:action/:id' + resources :customers + match ':controller/:action' end get :render_with_object_location diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 04e9acf855..1a03396ae9 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -403,7 +403,7 @@ class ResourcesTest < ActionController::TestCase with_restful_routing :messages do assert_restful_routes_for :messages do |options| assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) - assert_raise(ActionController::MethodNotAllowed) do + assert_raise(ActionController::RoutingError) do ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post) end end @@ -689,11 +689,11 @@ class ResourcesTest < ActionController::TestCase options = { :controller => controller_name.to_s } collection_path = "/#{controller_name}" - assert_raise(ActionController::MethodNotAllowed) do + assert_raise(ActionController::RoutingError) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) end - assert_raise(ActionController::MethodNotAllowed) do + assert_raise(ActionController::RoutingError) do assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete) end end @@ -1378,7 +1378,7 @@ class ResourcesTest < ActionController::TestCase end def assert_not_recognizes(expected_options, path) - assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Assertion do + assert_raise ActionController::RoutingError, Assertion do assert_recognizes(expected_options, path) end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 4eaf309c41..c15eaade58 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -51,30 +51,6 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end end -class RoutingTest < Test::Unit::TestCase - def test_normalize_unix_paths - load_paths = %w(. config/../app/controllers config/../app//helpers script/../config/../vendor/rails/actionpack/lib vendor/rails/railties/builtin/rails_info app/models lib script/../config/../foo/bar/../../app/models .foo/../.bar foo.bar/../config) - paths = ActionController::Routing.normalize_paths(load_paths) - assert_equal %w(vendor/rails/railties/builtin/rails_info vendor/rails/actionpack/lib app/controllers app/helpers app/models config .bar lib .), paths - end - - def test_normalize_windows_paths - load_paths = %w(. config\\..\\app\\controllers config\\..\\app\\\\helpers script\\..\\config\\..\\vendor\\rails\\actionpack\\lib vendor\\rails\\railties\\builtin\\rails_info app\\models lib script\\..\\config\\..\\foo\\bar\\..\\..\\app\\models .foo\\..\\.bar foo.bar\\..\\config) - paths = ActionController::Routing.normalize_paths(load_paths) - assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models config .bar lib .), paths - end - - def test_routing_helper_module - assert_kind_of Module, ActionController::Routing::Helpers - - h = ActionController::Routing::Helpers - c = Class.new - assert ! c.ancestors.include?(h) - ActionController::Routing::Routes.install_helpers c - assert c.ancestors.include?(h) - end -end - class MockController attr_accessor :routes @@ -578,10 +554,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase end def setup_request_method_routes_for(method) - @request = ActionController::TestRequest.new - @request.env["REQUEST_METHOD"] = method - @request.request_uri = "/match" - rs.draw do |r| r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get } r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post } @@ -593,8 +565,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase %w(GET POST PUT DELETE).each do |request_method| define_method("test_request_method_recognized_with_#{request_method}") do setup_request_method_routes_for(request_method) - assert_nothing_raised { rs.recognize(@request) } - assert_equal request_method.downcase, @request.path_parameters[:action] + params = rs.recognize_path("/match", :method => request_method) + assert_equal request_method.downcase, params[:action] end end @@ -604,18 +576,11 @@ class LegacyRouteSetTests < Test::Unit::TestCase r.connect '/match', :controller => 'books', :action => 'not_get_or_post' end - @request = ActionController::TestRequest.new - @request.env["REQUEST_METHOD"] = 'POST' - @request.request_uri = "/match" - assert_nothing_raised { rs.recognize(@request) } - assert_equal 'get_or_post', @request.path_parameters[:action] + params = rs.recognize_path("/match", :method => :post) + assert_equal 'get_or_post', params[:action] - # have to recreate or else the RouteSet uses a cached version: - @request = ActionController::TestRequest.new - @request.env["REQUEST_METHOD"] = 'PUT' - @request.request_uri = "/match" - assert_nothing_raised { rs.recognize(@request) } - assert_equal 'not_get_or_post', @request.path_parameters[:action] + params = rs.recognize_path("/match", :method => :put) + assert_equal 'not_get_or_post', params[:action] end def test_subpath_recognized @@ -769,9 +734,7 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do |map| map.connect '/users/index', :controller => :users, :action => :index end - @request = ActionController::TestRequest.new - @request.request_uri = '/users/index' - assert_nothing_raised { set.recognize(@request) } + params = set.recognize_path('/users/index', :method => :get) assert_equal 1, set.routes.size end @@ -1004,55 +967,34 @@ class RouteSetTest < ActiveSupport::TestCase end end - request.request_uri = "/people" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("index", request.path_parameters[:action]) - request.recycle! + params = set.recognize_path("/people", :method => :get) + assert_equal("index", params[:action]) - request.env["REQUEST_METHOD"] = "POST" - assert_nothing_raised { set.recognize(request) } - assert_equal("create", request.path_parameters[:action]) - request.recycle! + params = set.recognize_path("/people", :method => :post) + assert_equal("create", params[:action]) - request.env["REQUEST_METHOD"] = "PUT" - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) - request.recycle! + params = set.recognize_path("/people", :method => :put) + assert_equal("update", params[:action]) - assert_raise(ActionController::UnknownHttpMethod) { - request.env["REQUEST_METHOD"] = "BACON" - set.recognize(request) + assert_raise(ActionController::RoutingError) { + set.recognize_path("/people", :method => :bacon) } - request.recycle! - request.request_uri = "/people/5" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - request.recycle! + params = set.recognize_path("/people/5", :method => :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) - request.env["REQUEST_METHOD"] = "PUT" - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - request.recycle! + params = set.recognize_path("/people/5", :method => :put) + assert_equal("update", params[:action]) + assert_equal("5", params[:id]) - request.env["REQUEST_METHOD"] = "DELETE" - assert_nothing_raised { set.recognize(request) } - assert_equal("destroy", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - request.recycle! + params = set.recognize_path("/people/5", :method => :delete) + assert_equal("destroy", params[:action]) + assert_equal("5", params[:id]) - begin - request.env["REQUEST_METHOD"] = "POST" - set.recognize(request) - flunk 'Should have raised MethodNotAllowed' - rescue ActionController::MethodNotAllowed => e - assert_equal [:get, :put, :delete], e.allowed_methods - end - request.recycle! + assert_raise(ActionController::RoutingError) { + set.recognize_path("/people/5", :method => :post) + } end def test_recognize_with_alias_in_conditions @@ -1062,17 +1004,13 @@ class RouteSetTest < ActiveSupport::TestCase map.root :people end - request.path = "/people" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + params = set.recognize_path("/people", :method => :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) - request.path = "/" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + params = set.recognize_path("/", :method => :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) end def test_typo_recognition @@ -1082,14 +1020,12 @@ class RouteSetTest < ActiveSupport::TestCase :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ end - request.path = "/articles/2005/11/05/a-very-interesting-article" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("permalink", request.path_parameters[:action]) - assert_equal("2005", request.path_parameters[:year]) - assert_equal("11", request.path_parameters[:month]) - assert_equal("05", request.path_parameters[:day]) - assert_equal("a-very-interesting-article", request.path_parameters[:title]) + params = set.recognize_path("/articles/2005/11/05/a-very-interesting-article", :method => :get) + assert_equal("permalink", params[:action]) + assert_equal("2005", params[:year]) + assert_equal("11", params[:month]) + assert_equal("05", params[:day]) + assert_equal("a-very-interesting-article", params[:title]) end def test_routing_traversal_does_not_load_extra_classes @@ -1098,9 +1034,7 @@ class RouteSetTest < ActiveSupport::TestCase map.connect '/profile', :controller => 'profile' end - request.path = '/profile' - - set.recognize(request) rescue nil + params = set.recognize_path("/profile") rescue nil assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" end @@ -1114,24 +1048,17 @@ class RouteSetTest < ActiveSupport::TestCase end end - request.request_uri = "/people/5" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - request.recycle! + params = set.recognize_path("/people/5", :method => :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) - request.env["REQUEST_METHOD"] = "PUT" - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) - request.recycle! + params = set.recognize_path("/people/5", :method => :put) + assert_equal("update", params[:action]) - request.request_uri = "/people/5.png" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - assert_equal("png", request.path_parameters[:_format]) + params = set.recognize_path("/people/5.png", :method => :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) + assert_equal("png", params[:_format]) end def test_generate_with_default_action @@ -1147,11 +1074,9 @@ class RouteSetTest < ActiveSupport::TestCase def test_root_map set.draw { |map| map.root :controller => "people" } - request.path = "" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + params = set.recognize_path("", :method => :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) end def test_namespace @@ -1163,11 +1088,9 @@ class RouteSetTest < ActiveSupport::TestCase end - request.path = "/api/inventory" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("inventory", request.path_parameters[:action]) + params = set.recognize_path("/api/inventory", :method => :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) end def test_namespaced_root_map @@ -1179,11 +1102,9 @@ class RouteSetTest < ActiveSupport::TestCase end - request.path = "/api" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + params = set.recognize_path("/api", :method => :get) + assert_equal("api/products", params[:controller]) + assert_equal("index", params[:action]) end def test_namespace_with_path_prefix @@ -1193,11 +1114,9 @@ class RouteSetTest < ActiveSupport::TestCase end end - request.path = "/prefix/inventory" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("inventory", request.path_parameters[:action]) + params = set.recognize_path("/prefix/inventory", :method => :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) end def test_namespace_with_blank_path_prefix @@ -1207,11 +1126,9 @@ class RouteSetTest < ActiveSupport::TestCase end end - request.path = "/inventory" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("inventory", request.path_parameters[:action]) + params = set.recognize_path("/inventory", :method => :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) end def test_generate_changes_controller_module @@ -1340,11 +1257,9 @@ class RouteSetTest < ActiveSupport::TestCase end end - request.path = "/projects/1/milestones" - request.env["REQUEST_METHOD"] = "GET" - assert_nothing_raised { set.recognize(request) } - assert_equal("milestones", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + params = set.recognize_path("/projects/1/milestones", :method => :get) + assert_equal("milestones", params[:controller]) + assert_equal("index", params[:action]) end def test_setting_root_in_namespace_using_symbol @@ -1461,15 +1376,13 @@ class RouteSetTest < ActiveSupport::TestCase )/x} end - pending do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) - assert_equal "/page/david", url - assert_raise ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) - end - assert_raise ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) - end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) + assert_equal "/page/david", url + assert_raise ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + end + assert_raise ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) end end @@ -1485,10 +1398,8 @@ class RouteSetTest < ActiveSupport::TestCase )/xi} end - pending do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) - assert_equal "/page/JAMIS", url - end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + assert_equal "/page/JAMIS", url end def test_route_requirement_recognize_with_xi_modifiers @@ -1645,7 +1556,7 @@ class RouteSetTest < ActiveSupport::TestCase end def test_expand_array_build_query_string - assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) + assert_uri_equal '/foo?x[]=1&x[]=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) end def test_escape_spaces_build_query_string_selected_keys @@ -1663,9 +1574,7 @@ class RouteSetTest < ActiveSupport::TestCase map.connect ':controller/:action/:id' end - pending do - assert_equal '/ibocorp', set.generate({:controller => 'ibocorp', :page => 1}) - end + assert_equal '/ibocorp', set.generate({:controller => 'ibocorp', :page => 1}) end def test_generate_with_optional_params_recalls_last_request @@ -1698,9 +1607,7 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal("/blog/2006/07/25", set.generate({:day => 25}, last_request)) assert_equal("/blog/2005", set.generate({:year => 2005}, last_request)) assert_equal("/blog/show/123", set.generate({:action => "show" , :id => 123}, last_request)) - pending do - assert_equal("/blog/2006/07/28", set.generate({:year => 2006}, last_request)) - end + assert_equal("/blog/2006", set.generate({:year => 2006}, last_request)) assert_equal("/blog/2006", set.generate({:year => 2006, :month => nil}, last_request)) end @@ -1716,85 +1623,6 @@ class RouteSetTest < ActiveSupport::TestCase end end -class RouteLoadingTest < Test::Unit::TestCase - def setup - routes.instance_variable_set '@routes_last_modified', nil - Object.remove_const(:RAILS_ROOT) if defined?(::RAILS_ROOT) - Object.const_set :RAILS_ROOT, '.' - routes.add_configuration_file(File.join(RAILS_ROOT, 'config', 'routes.rb')) - - @stat = stub_everything - end - - def teardown - ActionController::Routing::Routes.configuration_files.clear - Object.send :remove_const, :RAILS_ROOT - end - - def test_load - File.expects(:stat).returns(@stat) - routes.expects(:load).with(regexp_matches(/routes\.rb$/)) - - routes.reload - end - - def test_no_reload_when_not_modified - @stat.expects(:mtime).times(2).returns(1) - File.expects(:stat).times(2).returns(@stat) - routes.expects(:load).with(regexp_matches(/routes\.rb$/)).at_most_once - - 2.times { routes.reload } - end - - def test_reload_when_modified - @stat.expects(:mtime).at_least(2).returns(1, 2) - File.expects(:stat).at_least(2).returns(@stat) - routes.expects(:load).with(regexp_matches(/routes\.rb$/)).times(2) - - 2.times { routes.reload } - end - - def test_bang_forces_reload - @stat.expects(:mtime).at_least(2).returns(1) - File.expects(:stat).at_least(2).returns(@stat) - routes.expects(:load).with(regexp_matches(/routes\.rb$/)).times(2) - - 2.times { routes.reload! } - end - - def test_adding_inflections_forces_reload - ActiveSupport::Inflector::Inflections.instance.expects(:uncountable).with('equipment') - routes.expects(:reload!) - - ActiveSupport::Inflector.inflections { |inflect| inflect.uncountable('equipment') } - end - - def test_load_with_configuration - routes.configuration_files.clear - routes.add_configuration_file("foobarbaz") - File.expects(:stat).returns(@stat) - routes.expects(:load).with("foobarbaz") - - routes.reload - end - - def test_load_multiple_configurations - routes.add_configuration_file("engines.rb") - - File.expects(:stat).at_least_once.returns(@stat) - - routes.expects(:load).with('./config/routes.rb') - routes.expects(:load).with('engines.rb') - - routes.reload - end - - private - def routes - ActionController::Routing::Routes - end -end - class RackMountIntegrationTests < ActiveSupport::TestCase Model = Struct.new(:to_param) @@ -1884,11 +1712,9 @@ class RackMountIntegrationTests < ActiveSupport::TestCase assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009'}, @routes.recognize_path('/blog/2009', :method => :get)) assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01'}, @routes.recognize_path('/blog/2009/01', :method => :get)) assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => '01'}, @routes.recognize_path('/blog/2009/01/01', :method => :get)) - assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/blog/123456789', :method => :get) } assert_equal({:controller => 'archive', :action => 'index', :year => '2010'}, @routes.recognize_path('/archive/2010')) assert_equal({:controller => 'archive', :action => 'index'}, @routes.recognize_path('/archive')) - assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/archive/january') } assert_equal({:controller => 'people', :action => 'index'}, @routes.recognize_path('/people', :method => :get)) assert_equal({:controller => 'people', :action => 'index', :format => 'xml'}, @routes.recognize_path('/people.xml', :method => :get)) @@ -2047,9 +1873,9 @@ class RackMountIntegrationTests < ActiveSupport::TestCase assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'}) assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'}) assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar') - assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz']) + assert_equal '/posts?foo[]=bar&foo[]=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz']) assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2) - assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}}) + assert_equal '/posts?q[foo][a]=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}}) assert_equal '/', @routes.generate(:controller => 'news', :action => 'index') assert_equal '/', @routes.generate(:controller => 'news', :action => 'index', :format => nil) diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 375878b755..0f074b32e6 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -456,8 +456,8 @@ XML def test_array_path_parameter_handled_properly with_routing do |set| set.draw do |map| - map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' - map.connect ':controller/:action/:id' + match 'file/*path', :to => 'test_test/test#test_params' + match ':controller/:action' end get :test_params, :path => ['hello', 'world'] @@ -563,7 +563,7 @@ XML expected = File.read(path) expected.force_encoding(Encoding::BINARY) if expected.respond_to?(:force_encoding) - file = ActionController::TestUploadedFile.new(path, content_type) + file = Rack::Test::UploadedFile.new(path, content_type) assert_equal filename, file.original_filename assert_equal content_type, file.content_type assert_equal file.path, file.local_path @@ -580,10 +580,10 @@ XML path = "#{FILES_DIR}/#{filename}" content_type = 'image/png' - binary_uploaded_file = ActionController::TestUploadedFile.new(path, content_type, :binary) + binary_uploaded_file = Rack::Test::UploadedFile.new(path, content_type, :binary) assert_equal File.open(path, READ_BINARY).read, binary_uploaded_file.read - plain_uploaded_file = ActionController::TestUploadedFile.new(path, content_type) + plain_uploaded_file = Rack::Test::UploadedFile.new(path, content_type) assert_equal File.open(path, READ_PLAIN).read, plain_uploaded_file.read end @@ -605,7 +605,7 @@ XML end def test_test_uploaded_file_exception_when_file_doesnt_exist - assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') } + assert_raise(RuntimeError) { Rack::Test::UploadedFile.new('non_existent_file') } end def test_redirect_url_only_cares_about_location_header @@ -628,17 +628,6 @@ XML assert_nothing_raised(NoMethodError) { @response.binary_content } end end - - protected - def with_foo_routing - with_routing do |set| - set.draw do |map| - map.generate_url 'foo', :controller => 'test' - map.connect ':controller/:action/:id' - end - yield set - end - end end class InferringClassNameTest < ActionController::TestCase @@ -673,7 +662,7 @@ class NamedRoutesControllerTest < ActionController::TestCase def test_should_be_able_to_use_named_routes_before_a_request_is_done with_routing do |set| - set.draw { |map| map.resources :contents } + set.draw { |map| resources :contents } assert_equal 'http://test.host/contents/new', new_content_url assert_equal 'http://test.host/contents/1', content_url(:id => 1) end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 3b14cbb2d8..428f40b9f8 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -247,7 +247,7 @@ class UrlWriterTests < ActionController::TestCase with_routing do |set| set.draw do |map| - map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' + match '/home/sweet/home/:user', :to => 'home#index', :as => :home end kls = Class.new { include ActionController::UrlWriter } @@ -264,7 +264,7 @@ class UrlWriterTests < ActionController::TestCase with_routing do |set| set.draw do |map| match 'home/sweet/home/:user', :to => 'home#index', :as => :home - map.connect ':controller/:action/:id' + match ':controller/:action/:id' end # We need to create a new class in order to install the new named route. @@ -331,8 +331,8 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes_with_nil_keys with_routing do |set| set.draw do |map| - map.main '', :controller => 'posts', :format => nil - map.resources :posts + match 'posts.:format', :to => 'posts#index', :as => :posts + match '/', :to => 'posts#index', :as => :main end # We need to create a new class in order to install the new named route. @@ -350,7 +350,7 @@ class UrlWriterTests < ActionController::TestCase def test_formatted_url_methods_are_deprecated with_routing do |set| set.draw do |map| - map.resources :posts + resources :posts end # We need to create a new class in order to install the new named route. kls = Class.new { include ActionController::UrlWriter } diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 0514c098bf..5882a8cfa3 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -255,9 +255,7 @@ class WebServiceTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - map.with_options :controller => "web_service_test/test" do |c| - c.connect "/", :action => "assign_parameters" - end + match '/', :to => 'web_service_test/test#assign_parameters' end yield end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 301080842e..40c5ac2d09 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -151,7 +151,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "multipart_params_parsing_test/test" + match ':action', :to => 'multipart_params_parsing_test/test' end yield end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 256ed06a45..02f63f7006 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -7,7 +7,6 @@ class ResponseTest < ActiveSupport::TestCase test "simple output" do @response.body = "Hello, World!" - @response.prepare! status, headers, body = @response.to_a assert_equal 200, status @@ -25,7 +24,6 @@ class ResponseTest < ActiveSupport::TestCase test "utf8 output" do @response.body = [1090, 1077, 1089, 1090].pack("U*") - @response.prepare! status, headers, body = @response.to_a assert_equal 200, status @@ -41,7 +39,6 @@ class ResponseTest < ActiveSupport::TestCase @response.body = Proc.new do |response, output| 5.times { |n| output.write(n) } end - @response.prepare! status, headers, body = @response.to_a assert_equal 200, status @@ -59,14 +56,12 @@ class ResponseTest < ActiveSupport::TestCase test "content type" do [204, 304].each do |c| @response.status = c.to_s - @response.prepare! status, headers, body = @response.to_a assert !headers.has_key?("Content-Type"), "#{c} should not have Content-Type header" end [200, 302, 404, 500].each do |c| @response.status = c.to_s - @response.prepare! status, headers, body = @response.to_a assert headers.has_key?("Content-Type"), "#{c} did not have Content-Type header" end @@ -74,7 +69,6 @@ class ResponseTest < ActiveSupport::TestCase test "does not include Status header" do @response.status = "200 OK" - @response.prepare! status, headers, body = @response.to_a assert !headers.has_key?('Status') end @@ -114,15 +108,126 @@ class ResponseTest < ActiveSupport::TestCase test "cookies" do @response.set_cookie("user_name", :value => "david", :path => "/") - @response.prepare! status, headers, body = @response.to_a assert_equal "user_name=david; path=/", headers["Set-Cookie"] assert_equal({"user_name" => "david"}, @response.cookies) @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5)) - @response.prepare! status, headers, body = @response.to_a assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT", headers["Set-Cookie"] assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies) end + + test "read cache control" do + resp = ActionDispatch::Response.new.tap { |resp| + resp.cache_control[:public] = true + resp.etag = '123' + resp.body = 'Hello' + } + resp.to_a + + assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag) + assert_equal({:public => true}, resp.cache_control) + + assert_equal('public', resp.headers['Cache-Control']) + assert_equal('"202cb962ac59075b964b07152d234b70"', resp.headers['ETag']) + end + + test "read charset and content type" do + resp = ActionDispatch::Response.new.tap { |resp| + resp.charset = 'utf-16' + resp.content_type = Mime::XML + resp.body = 'Hello' + } + resp.to_a + + assert_equal('utf-16', resp.charset) + assert_equal(Mime::XML, resp.content_type) + + assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type']) + end +end + +class ResponseIntegrationTest < ActionDispatch::IntegrationTest + def app + @app + end + + test "response cache control from railsish app" do + @app = lambda { |env| + ActionDispatch::Response.new.tap { |resp| + resp.cache_control[:public] = true + resp.etag = '123' + resp.body = 'Hello' + }.to_a + } + + get '/' + assert_response :success + + 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 + end + + test "response cache control from rackish app" do + @app = lambda { |env| + [200, + {'ETag' => '"202cb962ac59075b964b07152d234b70"', + 'Cache-Control' => 'public'}, ['Hello']] + } + + get '/' + assert_response :success + + 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 + end + + test "response charset and content type from railsish app" do + @app = lambda { |env| + ActionDispatch::Response.new.tap { |resp| + resp.charset = 'utf-16' + resp.content_type = Mime::XML + resp.body = 'Hello' + }.to_a + } + + get '/' + assert_response :success + + pending do + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) + end + + assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) + end + + test "response charset and content type from rackish app" do + @app = lambda { |env| + [200, + {'Content-Type' => 'application/xml; charset=utf-16'}, + ['Hello']] + } + + get '/' + assert_response :success + + pending do + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) + end + + assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) + end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 496445fc34..360ffe977b 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -14,7 +14,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes - Routes.draw do |map| + Routes.draw do controller :sessions do get 'login', :to => :new, :as => :login post 'login', :to => :create @@ -22,8 +22,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest delete 'logout', :to => :destroy, :as => :logout end + match 'account/logout' => redirect("/logout"), :as => :logout_redirect match 'account/login', :to => redirect("/login") + match 'account/modulo/:name', :to => redirect("/%{name}s") + match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" } + match 'openid/login', :via => [:get, :post], :to => "openid#login" controller(:global) do @@ -34,11 +38,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end constraints(:ip => /192\.168\.1\.\d\d\d/) do - get 'admin', :to => "queenbee#index" + get 'admin' => "queenbee#index" end constraints ::TestRoutingMapper::IpRestrictor do - get 'admin/accounts', :to => "queenbee#accounts" + get 'admin/accounts' => "queenbee#accounts" end resources :projects, :controller => :project do @@ -58,8 +62,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end resources :people do - namespace ":access_token" do - resource :avatar + nested do + namespace ":access_token" do + resource :avatar + end end member do @@ -80,7 +86,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - match 'sprockets.js', :to => ::TestRoutingMapper::SprocketsApp + match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp match 'people/:id/update', :to => 'people#update', :as => :update_person match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person @@ -93,9 +99,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end controller :articles do - scope 'articles' do - scope ':title', :title => /[a-z]+/, :as => :with_title do - match ':id', :to => :with_id + scope '/articles', :name_prefix => 'article' do + scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do + match '/:id', :to => :with_id end end end @@ -103,6 +109,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do resources :rooms end + + match '/info' => 'projects#info', :as => 'info' + + root :to => 'projects#index' end end @@ -143,6 +153,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_logout_redirect_without_to + with_test_routes do + assert_equal '/account/logout', logout_redirect_path + get '/account/logout' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/logout', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + + def test_redirect_modulo + with_test_routes do + get '/account/modulo/name' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/names', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + + def test_redirect_proc + with_test_routes do + get '/account/proc/person' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/people', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + def test_openid with_test_routes do get '/openid/login' @@ -194,14 +232,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'projects#index', @response.body assert_equal '/projects', projects_path + get '/projects.xml' + assert_equal 'projects#index', @response.body + assert_equal '/projects.xml', projects_path(:format => 'xml') + get '/projects/new' assert_equal 'projects#new', @response.body assert_equal '/projects/new', new_project_path + get '/projects/new.xml' + assert_equal 'projects#new', @response.body + assert_equal '/projects/new.xml', new_project_path(:format => 'xml') + get '/projects/1' assert_equal 'projects#show', @response.body assert_equal '/projects/1', project_path(:id => '1') + get '/projects/1.xml' + assert_equal 'projects#show', @response.body + assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') + get '/projects/1/edit' assert_equal 'projects#edit', @response.body assert_equal '/projects/1/edit', edit_project_path(:id => '1') @@ -214,9 +264,23 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'involvements#index', @response.body assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1') + get '/projects/1/involvements/new' + assert_equal 'involvements#new', @response.body + assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1') + get '/projects/1/involvements/1' assert_equal 'involvements#show', @response.body assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1') + + put '/projects/1/involvements/1' + assert_equal 'involvements#update', @response.body + + delete '/projects/1/involvements/1' + assert_equal 'involvements#destroy', @response.body + + get '/projects/1/involvements/1/edit' + assert_equal 'involvements#edit', @response.body + assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1') end end @@ -236,10 +300,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest put '/projects/1/participants/update_all' assert_equal 'participants#update_all', @response.body - - pending do - assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') - end + assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') end end @@ -251,15 +312,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/projects/1/companies/1/people' assert_equal 'people#index', @response.body - pending do - assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') - end + assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') get '/projects/1/companies/1/avatar' assert_equal 'avatars#show', @response.body - pending do - assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') - end + assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') end end @@ -271,9 +328,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest post '/projects/1/images/1/revise' assert_equal 'images#revise', @response.body - pending do - assert_equal '/projects/1/images/1/revise', revise_project_image_path(:project_id => '1', :id => '1') - end + assert_equal '/projects/1/images/1/revise', revise_project_image_path(:project_id => '1', :id => '1') end end @@ -289,27 +344,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/projects/1/people/1/7a2dec8/avatar' assert_equal 'avatars#show', @response.body - pending do - assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') - end + assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') put '/projects/1/people/1/accessible_projects' assert_equal 'people#accessible_projects', @response.body - pending do - assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') - end + assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') post '/projects/1/people/1/resend' assert_equal 'people#resend', @response.body - pending do - assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') - end + assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') post '/projects/1/people/1/generate_new_password' assert_equal 'people#generate_new_password', @response.body - pending do - assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') - end + assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') end end @@ -321,39 +368,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/projects/1/posts/archive' assert_equal 'posts#archive', @response.body - pending do - assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') - end + assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') get '/projects/1/posts/toggle_view' assert_equal 'posts#toggle_view', @response.body - pending do - assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') - end + assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') post '/projects/1/posts/1/preview' assert_equal 'posts#preview', @response.body - pending do - assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') - end + assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') get '/projects/1/posts/1/subscription' assert_equal 'subscriptions#show', @response.body - pending do - assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') - end + assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') get '/projects/1/posts/1/comments' assert_equal 'comments#index', @response.body - pending do - assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') - end + assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') post '/projects/1/posts/1/comments/preview' assert_equal 'comments#preview', @response.body - pending do - assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') - end + assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') end end @@ -411,7 +446,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_raise(ActionController::RoutingError) { get '/articles/123/1' } - assert_equal '/articles/rails/1', with_title_path(:title => 'rails', :id => 1) + assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1) end end @@ -428,6 +463,30 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_root + with_test_routes do + assert_equal '/', root_path + get '/' + assert_equal 'projects#index', @response.body + end + end + + def test_index + with_test_routes do + assert_equal '/info', info_path + get '/info' + assert_equal 'projects#info', @response.body + end + end + + def test_index + with_test_routes do + assert_equal '/info', info_path + get '/info' + assert_equal 'projects#info', @response.body + end + end + private def with_test_routes real_routes, temp_routes = ActionController::Routing::Routes, Routes diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 5da02b2ea6..e42ade73d1 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal "0.0.0.0", env.delete("REMOTE_ADDR") assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT") - assert_equal [1, 0], env.delete("rack.version") + assert_equal [1, 1], env.delete("rack.version") assert_equal "", env.delete("rack.input").string assert_kind_of StringIO, env.delete("rack.errors") assert_equal true, env.delete("rack.multithread") diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb index 250327e6dc..09692f77b5 100644 --- a/actionpack/test/lib/controller/fake_controllers.rb +++ b/actionpack/test/lib/controller/fake_controllers.rb @@ -1,37 +1,48 @@ class << Object; alias_method :const_available?, :const_defined?; end class ContentController < ActionController::Base; end -class NotAController; end module Admin class << self; alias_method :const_available?, :const_defined?; end + class AccountsController < ActionController::Base; end class NewsFeedController < ActionController::Base; end class PostsController < ActionController::Base; end class StuffController < ActionController::Base; end class UserController < ActionController::Base; end + class UsersController < ActionController::Base; end end module Api + class UsersController < ActionController::Base; end class ProductsController < ActionController::Base; end end # TODO: Reduce the number of test controllers we use +class AccountController < ActionController::Base; end class AddressesController < ActionController::Base; end +class ArchiveController < ActionController::Base; end class ArticlesController < ActionController::Base; end class BarController < ActionController::Base; end +class BlogController < ActionController::Base; end class BooksController < ActionController::Base; end class BraveController < ActionController::Base; end +class CarsController < ActionController::Base; end +class CcController < ActionController::Base; end class CController < ActionController::Base; end class ElsewhereController < ActionController::Base; end class FooController < ActionController::Base; end +class GeocodeController < ActionController::Base; end class HiController < ActionController::Base; end class ImageController < ActionController::Base; end +class NewsController < ActionController::Base; end class NotesController < ActionController::Base; end class PeopleController < ActionController::Base; end class PostsController < ActionController::Base; end class SessionsController < ActionController::Base; end class StuffController < ActionController::Base; end class SubpathBooksController < ActionController::Base; end +class SymbolsController < ActionController::Base; end +class UserController < ActionController::Base; end class WeblogController < ActionController::Base; end # For speed test diff --git a/actionpack/test/template/ajax_test.rb b/actionpack/test/template/ajax_test.rb index 670ba92697..aeb7c09b09 100644 --- a/actionpack/test/template/ajax_test.rb +++ b/actionpack/test/template/ajax_test.rb @@ -6,7 +6,7 @@ class AjaxTestCase < ActiveSupport::TestCase def assert_html(html, matches) matches.each do |match| - assert_match Regexp.new(Regexp.escape(match)), html + assert_match(Regexp.new(Regexp.escape(match)), html) end end @@ -52,18 +52,18 @@ class LinkToRemoteTest < AjaxTestCase test "with a hash for :update" do link = link(:update => {:success => "#posts", :failure => "#error"}) - assert_match /data-update-success="#posts"/, link - assert_match /data-update-failure="#error"/, link + assert_match(/data-update-success="#posts"/, link) + assert_match(/data-update-failure="#error"/, link) end test "with positional parameters" do link = link(:position => :top, :update => "#posts") - assert_match /data\-update\-position="top"/, link + assert_match(/data\-update\-position="top"/, link) end test "with an optional method" do link = link(:method => "delete") - assert_match /data-method="delete"/, link + assert_match(/data-method="delete"/, link) end class LegacyLinkToRemoteTest < AjaxTestCase @@ -99,7 +99,7 @@ class ButtonToRemoteTest < AjaxTestCase button = button({:url => {:action => "whatnot"}}, {:class => "fine"}) [/input/, /class="fine"/, /type="button"/, /value="Remote outpost"/, /data-url="\/whatnot"/].each do |match| - assert_match match, button + assert_match(match, button) end end end diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 6a5fb0acff..347cb98303 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -187,10 +187,9 @@ class ScrollsController < ActionController::Base end protected - - def rescue_action(e) - raise(e) - end + def rescue_action(e) + raise(e) + end end class AtomFeedTest < ActionController::TestCase @@ -311,11 +310,12 @@ class AtomFeedTest < ActionController::TestCase assert_select "summary div p", :text => "after 2" end end -private + + private def with_restful_routing(resources) with_routing do |set| set.draw do |map| - map.resources(resources) + resources(resources) end yield end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 35c51ca7ea..fdf3db1cdb 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -106,8 +106,8 @@ module RenderTestCases def test_render_partial_with_errors @view.render(:partial => "test/raise") - flunk "Render did not raise TemplateError" - rescue ActionView::TemplateError => e + flunk "Render did not raise Template::Error" + rescue ActionView::Template::Error => e assert_match "undefined local variable or method `doesnt_exist'", e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number @@ -116,8 +116,8 @@ module RenderTestCases def test_render_sub_template_with_errors @view.render(:file => "test/sub_template_raise") - flunk "Render did not raise TemplateError" - rescue ActionView::TemplateError => e + flunk "Render did not raise Template::Error" + rescue ActionView::Template::Error => e assert_match "undefined local variable or 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 diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 05a409d05a..9a448ce328 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -114,7 +114,7 @@ module ActionView test "is able to use named routes" do with_routing do |set| - set.draw { |map| map.resources :contents } + set.draw { |map| resources :contents } assert_equal 'http://test.host/contents/new', new_content_url assert_equal 'http://test.host/contents/1', content_url(:id => 1) end @@ -122,7 +122,7 @@ module ActionView test "named routes can be used from helper included in view" do with_routing do |set| - set.draw { |map| map.resources :contents } + set.draw { |map| resources :contents } _helpers.module_eval do def render_from_helper new_content_url diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index a20f3c394c..d67d2c7911 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -9,7 +9,7 @@ class TranslationHelperTest < Test::Unit::TestCase end def test_delegates_to_i18n_setting_the_raise_option - I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true) + I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true).returns("") translate :foo, :locale => 'en' end @@ -26,7 +26,7 @@ class TranslationHelperTest < Test::Unit::TestCase def test_scoping_by_partial expects(:template).returns(stub(:path_without_format_and_extension => "people/index")) - I18n.expects(:translate).with("people.index.foo", :locale => 'en', :raise => true) + I18n.expects(:translate).with("people.index.foo", :locale => 'en', :raise => true).returns("") translate ".foo", :locale => 'en' end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index bf0b4ad3a7..d4b58efe1e 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -391,54 +391,47 @@ class UrlHelperTest < ActionView::TestCase end end -class UrlHelperController < ActionController::Base - def self.controller_path; 'url_helper_with_controller' end +class UrlHelperControllerTest < ActionController::TestCase + class UrlHelperController < ActionController::Base + def show_url_for + render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>" + end - def show_url_for - render :inline => "<%= url_for :controller => 'url_helper_with_controller', :action => 'show_url_for' %>" - end + def show_named_route + render :inline => "<%= show_named_route_#{params[:kind]} %>" + end - def show_named_route - render :inline => "<%= show_named_route_#{params[:kind]} %>" - end + def nil_url_for + render :inline => '<%= url_for(nil) %>' + end - def nil_url_for - render :inline => '<%= url_for(nil) %>' + def rescue_action(e) raise e end end - def rescue_action(e) raise e end -end - -class UrlHelperWithControllerTest < ActionController::TestCase - def setup - super - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = UrlHelperController.new - end + tests UrlHelperController def test_url_for_shows_only_path get :show_url_for - assert_equal '/url_helper_with_controller/show_url_for', @response.body + assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body end def test_named_route_url_shows_host_and_path with_url_helper_routing do get :show_named_route, :kind => 'url' - assert_equal 'http://test.host/url_helper_with_controller/show_named_route', @response.body + assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route', @response.body end end def test_named_route_path_shows_only_path with_url_helper_routing do get :show_named_route, :kind => 'path' - assert_equal '/url_helper_with_controller/show_named_route', @response.body + assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body end end def test_url_for_nil_returns_current_path get :nil_url_for - assert_equal '/url_helper/nil_url_for', @response.body + assert_equal '/url_helper_controller_test/url_helper/nil_url_for', @response.body end def test_named_route_should_show_host_and_path_using_controller_default_url_options @@ -450,7 +443,7 @@ class UrlHelperWithControllerTest < ActionController::TestCase with_url_helper_routing do get :show_named_route, :kind => 'url' - assert_equal 'http://testtwo.host/url_helper_with_controller/show_named_route', @response.body + assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end end @@ -458,7 +451,7 @@ class UrlHelperWithControllerTest < ActionController::TestCase def with_url_helper_routing with_routing do |set| set.draw do |map| - map.show_named_route 'url_helper_with_controller/show_named_route', :controller => 'url_helper', :action => 'show_named_route' + match 'url_helper_controller_test/url_helper/show_named_route', :to => 'url_helper_controller_test/url_helper#show_named_route', :as => :show_named_route end yield end @@ -512,7 +505,7 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase def with_restful_routing with_routing do |set| set.draw do |map| - map.resources :tasks + resources :tasks end yield end @@ -632,8 +625,8 @@ class PolymorphicControllerTest < ActionController::TestCase def with_restful_routing with_routing do |set| set.draw do |map| - map.resources :workshops do |w| - w.resources :sessions + resources :workshops do + resources :sessions end end yield diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 1f4a8466c9..f098ce0671 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -15,7 +15,6 @@ task :default => :test Rake::TestTask.new do |t| t.libs << "#{dir}/test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort - t.verbose = true t.warning = true end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 505e16c195..e0de27b96d 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -21,33 +21,37 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift(activesupport_path) if File.directory?(activesupport_path) +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) require 'active_support' + module ActiveModel - autoload :AttributeMethods, 'active_model/attribute_methods' - autoload :Conversion, 'active_model/conversion' - autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods' - autoload :Dirty, 'active_model/dirty' - autoload :Errors, 'active_model/errors' - autoload :Lint, 'active_model/lint' - autoload :Name, 'active_model/naming' - autoload :Naming, 'active_model/naming' - autoload :Observer, 'active_model/observing' - autoload :Observing, 'active_model/observing' - autoload :Serialization, 'active_model/serialization' - autoload :StateMachine, 'active_model/state_machine' - autoload :TestCase, 'active_model/test_case' - autoload :Translation, 'active_model/translation' - autoload :Validations, 'active_model/validations' - autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper' - autoload :Validator, 'active_model/validator' - autoload :VERSION, 'active_model/version' + extend ActiveSupport::Autoload + + autoload :AttributeMethods + autoload :Conversion + autoload :DeprecatedErrorMethods + autoload :Dirty + autoload :Errors + autoload :Lint + autoload :Name, 'active_model/naming' + autoload :Naming + autoload :Observer, 'active_model/observing' + autoload :Observing + autoload :Serialization + autoload :StateMachine + autoload :Translation + autoload :Validations + autoload :ValidationsRepairHelper + autoload :Validator + autoload :VERSION module Serializers - autoload :JSON, 'active_model/serializers/json' - autoload :Xml, 'active_model/serializers/xml' + extend ActiveSupport::Autoload + + autoload :JSON + autoload :Xml end end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 977a101277..8f855958c6 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -235,6 +235,10 @@ module ActiveModel # It's also possible to instantiate related objects, so a Client class belonging to the clients # table with a +master_id+ foreign key can instantiate master through Client#master. def method_missing(method_id, *args, &block) + if method_id == :to_ary || method_id == :to_str + raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}" + end + method_name = method_id.to_s if match = match_attribute_method?(method_name) guard_private_attribute_method!(method_name, args) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 064ec98f3a..a0d64507f9 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/keys' +require 'active_model/errors' module ActiveModel module Validations diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index c1a3f6a4a7..30193956ea 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,15 +1,14 @@ -root = File.expand_path('../../../..', __FILE__) begin - require "#{root}/vendor/gems/environment" + require File.expand_path('../../../../vendor/gems/environment', __FILE__) rescue LoadError - $:.unshift("#{root}/activesupport/lib") end -lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +lib = File.expand_path('../../../lib', __FILE__) $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'config' require 'active_model' +require 'active_model/test_case' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb index 8dd00ea147..79668dd941 100644 --- a/activemodel/test/cases/tests_database.rb +++ b/activemodel/test/cases/tests_database.rb @@ -2,6 +2,7 @@ require 'logger' $:.unshift(File.dirname(__FILE__) + '/../../../activerecord/lib') require 'active_record' +require 'active_record/test_case' require 'active_record/fixtures' module ActiveModel diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d8bfb1916d..a2c5528860 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,65 @@ *Edge* +* Add find(ids) to relations. [Pratik Naik] + + old_users = User.order("age DESC") + old_users.find(1) + old_users.find(1, 2, 3) + +* Add new finder methods to association collection. [Pratik Naik] + + class User < ActiveRecord::Base + has_many :items + end + + user = User.first + user.items.where(:items => {:colour => 'red'}) + user.items.select('items.id') + +* Add relation.reload to force reloading the records. [Pratik Naik] + + topics = Topic.scoped + topics.to_a # force load + topics.first # returns a cached record + topics.reload + topics.first # Fetches a new record from the database + +* Rename Model.conditions and relation.conditions to .where. [Pratik Naik] + + Before : + User.conditions(:name => 'lifo') + User.select('id').conditions(["age > ?", 21]) + + Now : + User.where(:name => 'lifo') + User.select('id').where(["age > ?", 21]) + +* Add Model.select/group/order/limit/joins/conditions/preload/eager_load class methods returning a lazy relation. [Pratik Naik] + + Examples : + + posts = Post.select('id).order('name') # Returns a lazy relation + posts.each {|p| puts p.id } # Fires "select id from posts order by name" + +* Model.scoped now returns a relation if invoked without any arguments. [Pratik Naik] + + Example : + + posts = Post.scoped + posts.size # Fires "select count(*) from posts" and returns the count + posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects + +* Association inverses for belongs_to, has_one, and has_many. Optimization to reduce database queries. #3533 [Murray Steele] + + # post.comments sets each comment's post without needing to :include + class Post < ActiveRecord::Base + has_many :comments, :inverse_of => :post + end + +* MySQL: add_ and change_column support positioning. #3286 [Ben Marini] + +* Reset your Active Record counter caches with the reset_counter_cache class method. #1211 [Mike Breen, Gabe da Silveira] + * Remove support for SQLite 2. Please upgrade to SQLite 3+ or install the plugin from git://github.com/rails/sqlite2_adapter.git [Pratik Naik] * PostgreSQL: XML datatype support. #1874 [Leonardo Borges] diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 8195e78826..196b87c0ac 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -21,89 +21,108 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift(activesupport_path) if File.directory?(activesupport_path) -activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib" -$:.unshift(activemodel_path) if File.directory?(activemodel_path) +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) require 'active_support' require 'active_model' require 'arel' module ActiveRecord - # TODO: Review explicit loads to see if they will automatically be handled by the initializer. - def self.load_all! - [Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter] - end + extend ActiveSupport::Autoload + + eager_autoload do + autoload :VERSION - autoload :VERSION, 'active_record/version' - - autoload :ActiveRecordError, 'active_record/base' - autoload :ConnectionNotEstablished, 'active_record/base' - - autoload :Aggregations, 'active_record/aggregations' - autoload :AssociationPreload, 'active_record/association_preload' - autoload :Associations, 'active_record/associations' - autoload :AttributeMethods, 'active_record/attribute_methods' - autoload :Attributes, 'active_record/attributes' - autoload :AutosaveAssociation, 'active_record/autosave_association' - autoload :Relation, 'active_record/relation' - autoload :Base, 'active_record/base' - autoload :Batches, 'active_record/batches' - autoload :Calculations, 'active_record/calculations' - autoload :Callbacks, 'active_record/callbacks' - autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match' - autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match' - autoload :Migration, 'active_record/migration' - autoload :Migrator, 'active_record/migration' - autoload :NamedScope, 'active_record/named_scope' - autoload :NestedAttributes, 'active_record/nested_attributes' - autoload :Observer, 'active_record/observer' - autoload :QueryCache, 'active_record/query_cache' - autoload :Reflection, 'active_record/reflection' - autoload :Schema, 'active_record/schema' - autoload :SchemaDumper, 'active_record/schema_dumper' - autoload :Serialization, 'active_record/serialization' - autoload :SessionStore, 'active_record/session_store' - autoload :StateMachine, 'active_record/state_machine' - autoload :TestCase, 'active_record/test_case' - autoload :Timestamp, 'active_record/timestamp' - autoload :Transactions, 'active_record/transactions' - autoload :Types, 'active_record/types' - autoload :Validations, 'active_record/validations' + autoload :ActiveRecordError, 'active_record/base' + autoload :ConnectionNotEstablished, 'active_record/base' + + autoload :Aggregations + autoload :AssociationPreload + autoload :Associations + autoload :AttributeMethods + autoload :Attributes + autoload :AutosaveAssociation + autoload :Relation + autoload :Base + autoload :Batches + autoload :Calculations + autoload :Callbacks + autoload :DynamicFinderMatch + autoload :DynamicScopeMatch + autoload :Migration + autoload :Migrator, 'active_record/migration' + autoload :NamedScope + autoload :NestedAttributes + autoload :Observer + autoload :QueryCache + autoload :Reflection + autoload :Schema + autoload :SchemaDumper + autoload :Serialization + autoload :SessionStore + autoload :StateMachine + autoload :Timestamp + autoload :Transactions + autoload :Types + autoload :Validations + end module AttributeMethods - autoload :BeforeTypeCast, 'active_record/attribute_methods/before_type_cast' - autoload :Dirty, 'active_record/attribute_methods/dirty' - autoload :PrimaryKey, 'active_record/attribute_methods/primary_key' - autoload :Query, 'active_record/attribute_methods/query' - autoload :Read, 'active_record/attribute_methods/read' - autoload :TimeZoneConversion, 'active_record/attribute_methods/time_zone_conversion' - autoload :Write, 'active_record/attribute_methods/write' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write + end end module Attributes - autoload :Aliasing, 'active_record/attributes/aliasing' - autoload :Store, 'active_record/attributes/store' - autoload :Typecasting, 'active_record/attributes/typecasting' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Aliasing + autoload :Store + autoload :Typecasting + end end module Type - autoload :Number, 'active_record/types/number' - autoload :Object, 'active_record/types/object' - autoload :Serialize, 'active_record/types/serialize' - autoload :TimeWithZone, 'active_record/types/time_with_zone' - autoload :Unknown, 'active_record/types/unknown' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Number, 'active_record/types/number' + autoload :Object, 'active_record/types/object' + autoload :Serialize, 'active_record/types/serialize' + autoload :TimeWithZone, 'active_record/types/time_with_zone' + autoload :Unknown, 'active_record/types/unknown' + end end module Locking - autoload :Optimistic, 'active_record/locking/optimistic' - autoload :Pessimistic, 'active_record/locking/pessimistic' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Optimistic + autoload :Pessimistic + end end module ConnectionAdapters - autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter' + extend ActiveSupport::Autoload + + eager_autoload do + autoload :AbstractAdapter + end end end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0fcd288fc5..2735bc5141 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1325,7 +1325,7 @@ module ActiveRecord if association.nil? || force_reload association = association_proxy_class.new(self, reflection) - retval = association.reload + retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload if retval.nil? and association_proxy_class == BelongsToAssociation association_instance_set(reflection.name, nil) return nil @@ -1370,7 +1370,7 @@ module ActiveRecord association_instance_set(reflection.name, association) end - association.reload if force_reload + reflection.klass.uncached { association.reload } if force_reload association end @@ -1382,9 +1382,9 @@ module ActiveRecord if reflection.through_reflection && reflection.source_reflection.belongs_to? through = reflection.through_reflection primary_key = reflection.source_reflection.primary_key_name - send(through.name).all(:select => "DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") + send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) end end end @@ -1717,9 +1717,9 @@ module ActiveRecord select(column_aliases(join_dependency)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). - conditions(construct_conditions(options[:conditions], scope)) + where(construct_conditions(options[:conditions], scope)) - relation = relation.conditions(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) relation @@ -1757,7 +1757,7 @@ module ActiveRecord end relation = relation.joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 25e329c0c1..b85d76e8d3 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -20,7 +20,22 @@ module ActiveRecord super construct_sql end - + + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped + + def select(select = nil, &block) + if block_given? + load_target + @target.select(&block) + else + scoped.select(select) + end + end + + def scoped + with_scope(construct_scope) { @reflection.klass.scoped } + end + def find(*args) options = args.extract_options! @@ -383,7 +398,7 @@ module ActiveRecord loaded if target target end - + def method_missing(method, *args) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) if block_given? diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index b01faa5212..9569b0c6f9 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -71,7 +71,7 @@ module ActiveRecord records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) } else relation = arel_table(@reflection.options[:join_table]) - relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) ).delete end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index cd31b0e211..be74ddfcf0 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -70,7 +70,7 @@ module ActiveRecord @reflection.klass.delete(records.map { |record| record.id }) else relation = arel_table(@reflection.table_name) - relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id))) ).update(relation[@reflection.primary_key_name] => nil) diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 8f37fcd515..c0d8904bc8 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -221,9 +221,9 @@ module ActiveRecord if new_record association elsif association.loaded? - autosave ? association : association.select { |record| record.new_record? } + autosave ? association : association.find_all { |record| record.new_record? } else - autosave ? association.target : association.target.select { |record| record.new_record? } + autosave ? association.target : association.target.find_all { |record| record.new_record? } end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 056f29f029..2008dea5e9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -13,6 +13,7 @@ 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/module/delegation' module ActiveRecord #:nodoc: # Generic Active Record exception class. @@ -650,6 +651,8 @@ module ActiveRecord #:nodoc: end end + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped + # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. def first(*args) @@ -668,20 +671,10 @@ module ActiveRecord #:nodoc: options = args.extract_options! if options.empty? && !scoped?(:find) - relation = arel_table + arel_table.to_a else - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation.eager_load(include_associations) - else - relation.preload(include_associations) - end - end + construct_finder_arel_with_includes(options).to_a end - relation end # Executes a custom SQL query against your database and returns all the results. The results will @@ -882,7 +875,7 @@ module ActiveRecord #:nodoc: relation = arel_table if conditions = construct_conditions(conditions, scope) - relation = relation.conditions(Arel::SqlLiteral.new(conditions)) + relation = relation.where(Arel::SqlLiteral.new(conditions)) end relation = if options.has_key?(:limit) || (scope && scope[:limit]) @@ -945,7 +938,7 @@ module ActiveRecord #:nodoc: # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) if conditions - arel_table.conditions(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete + arel_table.where(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete else arel_table.delete end @@ -967,6 +960,29 @@ module ActiveRecord #:nodoc: connection.select_value(sql, "#{name} Count").to_i end + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more counter names to reset + # + # ==== Examples + # + # # For Post with id #1 records reset the comments_count + # Post.reset_counters(1, :comments) + def reset_counters(id, *counters) + object = find(id) + counters.each do |association| + child_class = reflect_on_association(association).klass + counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column + + connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") + end + end + # A generic "counter updater" implementation, intended primarily to be # used by increment_counter and decrement_counter, but which may also # be useful on its own. It simply does a direct SQL update for the record @@ -1491,13 +1507,8 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end - def arel_table(table = nil) - table = table_name if table.blank? - if @arel_table.nil? || @arel_table.name != table - @arel_table = Relation.new(self, Arel::Table.new(table)) - end - @arel_table + Relation.new(self, Arel::Table.new(table || table_name)) end private @@ -1666,9 +1677,11 @@ module ActiveRecord #:nodoc: def construct_finder_arel(options = {}, scope = scope(:find)) # TODO add lock to Arel + validate_find_options(options) + relation = arel_table(options[:from]). joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). @@ -1680,6 +1693,21 @@ module ActiveRecord #:nodoc: relation end + def construct_finder_arel_with_includes(options = {}) + relation = construct_finder_arel(options) + include_associations = merge_includes(scope(:find, :include), options[:include]) + + if include_associations.any? + if references_eager_loaded_tables?(options) + relation = relation.eager_load(include_associations) + else + relation = relation.preload(include_associations) + end + end + + relation + end + def construct_finder_sql(options, scope = scope(:find)) construct_finder_arel(options, scope).to_sql end @@ -1814,9 +1842,8 @@ module ActiveRecord #:nodoc: end # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt> - # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and - # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for - # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>. + # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt> + # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>. # # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ # is actually <tt>find_all_by_amount(amount, options)</tt>. @@ -1832,103 +1859,11 @@ module ActiveRecord #:nodoc: attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) if match.finder? - finder = match.finder - bang = match.bang? - # def self.find_by_login_and_activated(*args) - # options = args.extract_options! - # attributes = construct_attributes_from_arguments( - # [:login,:activated], - # args - # ) - # finder_options = { :conditions => attributes } - # validate_find_options(options) - # set_readonly_option!(options) - # - # if options[:conditions] - # with_scope(:find => finder_options) do - # find(:first, options) - # end - # else - # find(:first, options.merge(finder_options)) - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - options = args.extract_options! - attributes = construct_attributes_from_arguments( - [:#{attribute_names.join(',:')}], - args - ) - finder_options = { :conditions => attributes } - validate_find_options(options) - set_readonly_option!(options) - - #{'result = ' if bang}if options[:conditions] - with_scope(:find => finder_options) do - find(:#{finder}, options) - end - else - find(:#{finder}, options.merge(finder_options)) - end - #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect { |pair| pair.join(\' = \') }.join(\', \')}")' if bang} - end - }, __FILE__, __LINE__ - send(method_id, *arguments) + options = arguments.extract_options! + relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? - instantiator = match.instantiator - # def self.find_or_create_by_user_id(*args) - # guard_protected_attributes = false - # - # if args[0].is_a?(Hash) - # guard_protected_attributes = true - # attributes = args[0].with_indifferent_access - # find_attributes = attributes.slice(*[:user_id]) - # else - # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args) - # end - # - # options = { :conditions => find_attributes } - # set_readonly_option!(options) - # - # record = find(:first, options) - # - # if record.nil? - # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - # yield(record) if block_given? - # record.save - # record - # else - # record - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - guard_protected_attributes = false - - if args[0].is_a?(Hash) - guard_protected_attributes = true - attributes = args[0].with_indifferent_access - find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}]) - else - find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) - end - - options = { :conditions => find_attributes } - set_readonly_option!(options) - - record = find(:first, options) - - if record.nil? - record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - #{'yield(record) if block_given?'} - #{'record.save' if instantiator == :create} - record - else - record - end - end - }, __FILE__, __LINE__ - send(method_id, *arguments, &block) + scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end elsif match = DynamicScopeMatch.match(method_id) attribute_names = match.attribute_names @@ -2543,7 +2478,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).delete + self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete end @destroyed = true @@ -2830,7 +2765,7 @@ module ActiveRecord #:nodoc: def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 40242333e5..fcba23dc0d 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -148,7 +148,7 @@ module ActiveRecord else relation = arel_table(options[:from]). joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). order(options[:order]). limit(options[:limit]). offset(options[:offset]) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index ad36ff22e3..fa28bc64df 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -470,6 +470,13 @@ module ActiveRecord execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end + def add_column(table_name, column_name, type, options = {}) + add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(add_column_sql, options) + add_column_position!(add_column_sql, options) + execute(add_column_sql) + end + def change_column_default(table_name, column_name, default) #:nodoc: column = column_for(table_name, column_name) change_column table_name, column_name, column.sql_type, :default => default @@ -498,6 +505,7 @@ module ActiveRecord change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) execute(change_column_sql) end @@ -529,6 +537,13 @@ module ActiveRecord end end + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + end # SHOW VARIABLES LIKE 'name' def show_variable(name) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index bbe2d1f205..a6336e762a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -6,18 +6,34 @@ module ActiveRecord module NamedScope extend ActiveSupport::Concern - # All subclasses of ActiveRecord::Base have one named scope: - # * <tt>scoped</tt> - which allows for the creation of anonymous \scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt> - # - # These anonymous \scopes tend to be useful when procedurally generating complex queries, where passing - # intermediate values (scopes) around as first-class objects is convenient. - # - # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. - included do - named_scope :scoped, lambda { |scope| scope } - end - module ClassMethods + # Returns a relation if invoked without any arguments. + # + # posts = Post.scoped + # posts.size # Fires "select count(*) from posts" and returns the count + # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects + # + # Returns an anonymous named scope if any options are supplied. + # + # shirts = Shirt.scoped(:conditions => {:color => 'red'}) + # shirts = shirts.scoped(:include => :washing_instructions) + # + # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing + # intermediate values (scopes) around as first-class objects is convenient. + # + # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. + def scoped(options = {}, &block) + if options.present? + Scope.new(self, options, &block) + else + unless scoped?(:find) + finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table + else + construct_finder_arel_with_includes + end + end + end + def scopes read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) end diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb deleted file mode 100644 index 562a5b91f4..0000000000 --- a/activerecord/lib/active_record/notifications.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/notifications' - -ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| - ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) -end diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb new file mode 100644 index 0000000000..ddbc555113 --- /dev/null +++ b/activerecord/lib/active_record/rails.rb @@ -0,0 +1,59 @@ +# For now, action_controller must always be present with +# rails, so let's make sure that it gets required before +# here. This is needed for correctly setting up the middleware. +# In the future, this might become an optional require. +require "action_controller/rails" + +module ActiveRecord + class Plugin < Rails::Plugin + plugin_name :active_record + + initializer "active_record.set_configs" do |app| + app.config.active_record.each do |k,v| + ActiveRecord::Base.send "#{k}=", v + 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 + end + + initializer "active_record.initialize_timezone" do + ActiveRecord::Base.time_zone_aware_attributes = true + ActiveRecord::Base.default_timezone = :utc + end + + # Setup database middleware after initializers have run + initializer "active_record.initialize_database_middleware" do |app| + middleware = app.config.middleware + if middleware.include?(ActiveRecord::SessionStore) + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::QueryCache + else + middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.use ActiveRecord::QueryCache + end + end + + initializer "active_record.load_observers" do + ActiveRecord::Base.instantiate_observers + end + + # TODO: ActiveRecord::Base.logger should delegate to its own config.logger + initializer "active_record.logger" do + ActiveRecord::Base.logger ||= Rails.logger + end + + initializer "active_record.notifications" do + require 'active_support/notifications' + + ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| + ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) + end + end + + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5f0eec754f..c927270eee 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -4,37 +4,94 @@ module ActiveRecord delegate :length, :collect, :find, :map, :each, :to => :to_a attr_reader :relation, :klass - def initialize(klass, relation) + def initialize(klass, relation, readonly = false, preload = [], eager_load = []) @klass, @relation = klass, relation - @readonly = false - @associations_to_preload = [] - @eager_load_associations = [] + @readonly = readonly + @associations_to_preload = preload + @eager_load_associations = eager_load + @loaded = false end - def preload(association) - @associations_to_preload += association - self + def preload(*associations) + create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) end - def eager_load(association) - @eager_load_associations += association - self + def eager_load(*associations) + create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) end def readonly - @readonly = true - self + create_new_relation(@relation, true) + end + + def select(selects) + create_new_relation(@relation.project(selects)) + end + + def group(groups) + create_new_relation(@relation.group(groups)) + end + + def order(orders) + create_new_relation(@relation.order(orders)) + end + + def limit(limits) + create_new_relation(@relation.take(limits)) + end + + def offset(offsets) + create_new_relation(@relation.skip(offsets)) + end + + def on(join) + create_new_relation(@relation.on(join)) + end + + def joins(join, join_type = nil) + return self if join.blank? + + join_relation = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) + else + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) + end + + create_new_relation(join_relation) + end + + def where(*args) + if [String, Hash, Array].include?(args.first.class) + conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + else + conditions = args.first + end + + create_new_relation(@relation.where(conditions)) + end + + def respond_to?(method) + @relation.respond_to?(method) || Array.method_defined?(method) || super end def to_a - records = if @eager_load_associations.any? + return @records if loaded? + + @records = if @eager_load_associations.any? catch :invalid_query do return @klass.send(:find_with_associations, { :select => @relation.send(:select_clauses).join(', '), :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), :order => @relation.send(:order_clauses).join(', '), - :conditions => @relation.send(:where_clauses).join("\n\tAND "), + :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped }, @@ -45,83 +102,151 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @klass.send(:preload_associations, records, @associations_to_preload) unless @associations_to_preload.empty? - records.each { |record| record.readonly! } if @readonly + @associations_to_preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @records.each { |record| record.readonly! } if @readonly - records + @loaded = true + @records end - def first - @relation = @relation.take(1) - to_a.first - end + alias all to_a - def select(selects) - selects.blank? ? self : Relation.new(@klass, @relation.project(selects)) - end + def find(*ids, &block) + return to_a.find(&block) if block_given? - def group(groups) - groups.blank? ? self : Relation.new(@klass, @relation.group(groups)) + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end end - def order(orders) - orders.blank? ? self : Relation.new(@klass, @relation.order(orders)) + def first + if loaded? + @records.first + else + @first ||= limit(1).to_a[0] + end end - def limit(limits) - limits.blank? ? self : Relation.new(@klass, @relation.take(limits)) + def loaded? + @loaded end - def offset(offsets) - offsets.blank? ? self : Relation.new(@klass, @relation.skip(offsets)) + def reload + @loaded = false + @records = @first = nil + self end - def on(join) - join.blank? ? self : Relation.new(@klass, @relation.on(join)) + protected + + def method_missing(method, *args, &block) + if @relation.respond_to?(method) + @relation.send(method, *args, &block) + elsif Array.method_defined?(method) + to_a.send(method, *args, &block) + elsif match = DynamicFinderMatch.match(method) + attributes = match.attribute_names + super unless @klass.send(:all_attributes_exists?, attributes) + + if match.finder? + find_by_attributes(match, attributes, *args) + elsif match.instantiator? + find_or_instantiator_by_attributes(match, attributes, *args, &block) + end + else + super + end end - def joins(join, join_type = nil) - if join.blank? - self + def find_by_attributes(match, attributes, *args) + conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + result = where(conditions).send(match.finder) + + if match.bang? && result.blank? + raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else - join = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) - else - @relation.join(@klass.send(:build_association_joins, join)) - end - else - @relation.join(join, join_type) - end - Relation.new(@klass, join) + result end end - def conditions(conditions) - if conditions.blank? - self + def find_or_instantiator_by_attributes(match, attributes, *args) + guard_protected_attributes = false + + if args[0].is_a?(Hash) + guard_protected_attributes = true + attributes_for_create = args[0].with_indifferent_access + conditions = attributes_for_create.slice(*attributes).symbolize_keys else - conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) - Relation.new(@klass, @relation.where(conditions)) + attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} end + + record = where(conditions).first + + unless record + record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) } + yield(record) if block_given? + record.save if match.instantiator == :create + end + + record end - def respond_to?(method) - @relation.respond_to?(method) || Array.method_defined?(method) || super + def find_one(id) + record = where(@klass.primary_key => id).first + + unless record + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" + end + + record end - private - def method_missing(method, *args, &block) - if @relation.respond_to?(method) - @relation.send(method, *args, &block) - elsif Array.method_defined?(method) - to_a.send(method, *args, &block) + def find_some(ids) + result = where(@klass.primary_key => ids).all + + expected_size = + if @relation.taken && ids.size > @relation.taken + @relation.taken else - super + ids.size end + + # 11 ids with limit 3, offset 9 should give 2 results. + if @relation.skipped && (ids.size - @relation.skipped < expected_size) + expected_size = ids.size - @relation.skipped + end + + if result.size == expected_size + result + else + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + + error = "Couldn't find all #{@klass.name.pluralize} with IDs " + error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" + raise RecordNotFound, error end + end + + def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) + Relation.new(@klass, relation, readonly, preload, eager_load) + end + + def where_clause(join_string = "\n\tAND ") + @relation.send(:where_clauses).join(join_string) + 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 86d14c9c81..ce7eedbb54 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1179,4 +1179,3 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal firm.name, client.firm_name end end - diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index fe68d03de2..608d5a3608 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -140,23 +140,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_replace_order_is_preserved posts(:welcome).people.clear posts(:welcome).people = [people(:david), people(:michael)] - assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).people = [people(:michael), people(:david)] - assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) end def test_replace_by_id_order_is_preserved posts(:welcome).people.clear posts(:welcome).person_ids = [people(:david).id, people(:michael).id] - assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.order('id').map(&:person_id) # Test the inverse order in case the first success was a coincidence posts(:welcome).people.clear posts(:welcome).person_ids = [people(:michael).id, people(:david).id] - assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.order('id').map(&:person_id) end def test_associate_with_create diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index e429c1d157..9bc34bd750 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -64,6 +64,16 @@ class AssociationsTest < ActiveRecord::TestCase assert !firm.clients(true).empty?, "New firm should have reloaded client objects" assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" end + + def test_force_reload_is_uncached + firm = Firm.create!("name" => "A New Firm, Inc") + client = Client.create!("name" => "TheClient.com", :firm => firm) + ActiveRecord::Base.cache do + firm.clients.each {} + assert_queries(0) { assert_not_nil firm.clients.each {} } + assert_queries(1) { assert_not_nil firm.clients(true).each {} } + end + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 5c2911eca1..b51c9f0cb3 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -680,6 +680,16 @@ class BasicsTest < ActiveRecord::TestCase assert_equal -2, Topic.find(2).replies_count end + def test_reset_counters + assert_equal 1, Topic.find(1).replies_count + + Topic.increment_counter("replies_count", 1) + assert_equal 2, Topic.find(1).replies_count + + Topic.reset_counters(1, :replies) + assert_equal 1, Topic.find(1).replies_count + end + def test_update_counter category = categories(:general) assert_nil category.categorizations_count @@ -1892,8 +1902,14 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Developer.find(:first, :order => 'id desc'), Developer.last end + def test_all + developers = Developer.all + assert_kind_of Array, developers + assert_equal Developer.find(:all), developers + end + def test_all_with_conditions - assert_equal Developer.find(:all, :order => 'id desc'), Developer.all.order('id desc').to_a + assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all end def test_find_ordered_last diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 5009a90846..e417d8a803 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -5,7 +5,7 @@ class EachTest < ActiveRecord::TestCase fixtures :posts def setup - @posts = Post.all(:order => "id asc") + @posts = Post.order("id asc") @total = Post.count end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index f456d273fe..4961d12a44 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -301,7 +301,7 @@ class DirtyTest < ActiveRecord::TestCase def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present with_partial_updates(Topic) do Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) - topic = Topic.first(:select => 'id, author_name') + topic = Topic.select('id, author_name').first topic.update_attribute :author_name, 'John' topic = Topic.first assert_not_nil topic.content diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 3de07797d4..c531a2dec1 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -233,11 +233,11 @@ class FinderTest < ActiveRecord::TestCase end def test_first - assert_equal topics(:second).title, Topic.first(:conditions => "title = 'The Second Topic of the day'").title + assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title end def test_first_failing - assert_nil Topic.first(:conditions => "title = 'The Second Topic of the day!'") + assert_nil Topic.where("title = 'The Second Topic of the day!'").first end def test_unexisting_record_exception_handling @@ -291,7 +291,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_hash_conditions_on_joined_table - firms = Firm.all :joins => :account, :conditions => {:accounts => { :credit_limit => 50 }} + firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 }) assert_equal 1, firms.size assert_equal companies(:first_firm), firms.first end @@ -571,21 +571,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) end - def test_dynamic_finders_should_go_through_the_find_class_method - Topic.expects(:find).with(:first, :conditions => { :title => 'The First Topic!' }) - Topic.find_by_title("The First Topic!") - - Topic.expects(:find).with(:last, :conditions => { :title => 'The Last Topic!' }) - Topic.find_last_by_title("The Last Topic!") - - Topic.expects(:find).with(:all, :conditions => { :title => 'A Topic.' }) - Topic.find_all_by_title("A Topic.") - - Topic.expects(:find).with(:first, :conditions => { :title => 'Does not exist yet for sure!' }).times(2) - Topic.find_or_initialize_by_title('Does not exist yet for sure!') - Topic.find_or_create_by_title('Does not exist yet for sure!') - end - def test_find_by_one_attribute assert_equal topics(:first), Topic.find_by_title("The First Topic") assert_nil Topic.find_by_title("The First Topic!") @@ -596,21 +581,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } end - def test_find_by_one_attribute_caches_dynamic_finder - # ensure this test can run independently of order - class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.public_methods.any? { |m| m.to_s == 'find_by_title' } - assert !Topic.public_methods.any? { |m| m.to_s == 'find_by_title' } - t = Topic.find_by_title("The First Topic") - assert Topic.public_methods.any? { |m| m.to_s == 'find_by_title' } - end - - def test_dynamic_finder_returns_same_results_after_caching - # ensure this test can run independently of order - class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.public_method_defined?(:find_by_title) - t = Topic.find_by_title("The First Topic") - assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached - end - def test_find_by_one_attribute_with_order_option assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id') assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC') @@ -654,14 +624,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal customers(:david), found_customer end - def test_dynamic_finder_on_one_attribute_with_conditions_caches_method - # ensure this test can run independently of order - class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } - assert !Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } - a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) - assert Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } - end - def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } @@ -694,14 +656,6 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.find_last_by_title("A title with no matches") end - def test_find_last_by_one_attribute_caches_dynamic_finder - # ensure this test can run independently of order - class << Topic; self; end.send(:remove_method, :find_last_by_title) if Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' } - assert !Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' } - t = Topic.find_last_by_title(Topic.last.title) - assert Topic.public_methods.any? { |m| m.to_s == 'find_last_by_title' } - end - def test_find_last_by_invalid_method_syntax assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") } assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") } @@ -926,13 +880,6 @@ class FinderTest < ActiveRecord::TestCase assert !c.new_record? end - def test_dynamic_find_or_initialize_from_one_attribute_caches_method - class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } - assert !Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } - sig38 = Company.find_or_initialize_by_name("38signals") - assert Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } - end - def test_find_or_initialize_from_two_attributes another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") assert_equal "Another topic", another.title diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 25613da912..307320b964 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,11 +1,9 @@ -root = File.expand_path('../../../..', __FILE__) begin - require "#{root}/vendor/gems/environment" + require File.expand_path('../../../../vendor/gems/environment', __FILE__) rescue LoadError - $:.unshift("#{root}/activesupport/lib") end -lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +lib = File.expand_path('../../../lib', __FILE__) $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'config' diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6d3f938799..0ef34e440a 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -527,6 +527,53 @@ if ActiveRecord::Base.connection.supports_migrations? assert !Person.column_methods_hash.include?(:last_name) end + if current_adapter?(:MysqlAdapter) + def testing_table_for_positioning + Person.connection.create_table :testings, :id => false do |t| + t.column :first, :integer + t.column :second, :integer + t.column :third, :integer + end + + yield Person.connection + ensure + Person.connection.drop_table :testings rescue nil + end + protected :testing_table_for_positioning + + def test_column_positioning + testing_table_for_positioning do |conn| + assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } + end + end + + def test_add_column_with_positioning + testing_table_for_positioning do |conn| + conn.add_column :testings, :new_col, :integer + assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } + end + testing_table_for_positioning do |conn| + conn.add_column :testings, :new_col, :integer, :first => true + assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } + end + testing_table_for_positioning do |conn| + conn.add_column :testings, :new_col, :integer, :after => :first + assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } + end + end + + def test_change_column_with_positioning + testing_table_for_positioning do |conn| + conn.change_column :testings, :second, :integer, :first => true + assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } + end + testing_table_for_positioning do |conn| + conn.change_column :testings, :second, :integer, :after => :third + assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + end + end + end + def test_add_rename Person.delete_all diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 13427daf53..5d9232bc52 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -345,8 +345,8 @@ class NamedScopeTest < ActiveRecord::TestCase def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions - assert_equal Topic.all(:conditions => {:approved => true}).to_a, Topic.rejected.approved.all.to_a - assert_equal Topic.all(:conditions => {:approved => false}).to_a, Topic.approved.rejected.all.to_a + assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all.to_a + assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all.to_a # Nested hash conditions with same keys assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 1a2c8030fb..9c5a38a399 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1,4 +1,6 @@ require "cases/helper" +require 'models/tag' +require 'models/tagging' require 'models/post' require 'models/topic' require 'models/comment' @@ -10,50 +12,112 @@ require 'models/developer' require 'models/company' class RelationTest < ActiveRecord::TestCase - fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments + fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, + :taggings + + def test_scoped + topics = Topic.scoped + assert_kind_of ActiveRecord::Relation, topics + assert_equal 4, topics.size + end + + def test_scoped_all + topics = Topic.scoped.all + assert_kind_of Array, topics + assert_no_queries { assert_equal 4, topics.size } + end + + def test_loaded_all + topics = Topic.scoped + + assert_queries(1) do + 2.times { assert_equal 4, topics.all.size } + end + + assert topics.loaded? + end + + def test_scoped_first + topics = Topic.scoped + + assert_queries(1) do + 2.times { assert_equal "The First Topic", topics.first.title } + end + + assert ! topics.loaded? + end + + def test_loaded_first + topics = Topic.scoped + + assert_queries(1) do + topics.all # force load + 2.times { assert_equal "The First Topic", topics.first.title } + end + + assert topics.loaded? + end + + def test_reload + topics = Topic.scoped + + assert_queries(1) do + 2.times { topics.to_a } + end + + assert topics.loaded? + + topics.reload + assert ! topics.loaded? + + assert_queries(1) { topics.to_a } + end def test_finding_with_conditions - assert_equal Author.find(:all, :conditions => "name = 'David'"), Author.all.conditions("name = 'David'").to_a + assert_equal ["David"], Author.where(:name => 'David').map(&:name) + assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) + assert_equal ['Mary'], Author.where("name = ?", 'Mary').map(&:name) end def test_finding_with_order - topics = Topic.all.order('id') + topics = Topic.order('id') assert_equal 4, topics.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_order_and_take - entrants = Entrant.all.order("id ASC").limit(2).to_a + entrants = Entrant.order("id ASC").limit(2).to_a - assert_equal(2, entrants.size) - assert_equal(entrants(:first).name, entrants.first.name) + assert_equal 2, entrants.size + assert_equal entrants(:first).name, entrants.first.name end def test_finding_with_order_limit_and_offset - entrants = Entrant.all.order("id ASC").limit(2).offset(1) + entrants = Entrant.order("id ASC").limit(2).offset(1) - assert_equal(2, entrants.size) - assert_equal(entrants(:second).name, entrants.first.name) + assert_equal 2, entrants.size + assert_equal entrants(:second).name, entrants.first.name - entrants = Entrant.all.order("id ASC").limit(2).offset(2) - assert_equal(1, entrants.size) - assert_equal(entrants(:third).name, entrants.first.name) + entrants = Entrant.order("id ASC").limit(2).offset(2) + assert_equal 1, entrants.size + assert_equal entrants(:third).name, entrants.first.name end def test_finding_with_group - developers = Developer.all.group("salary").select("salary").to_a + developers = Developer.group("salary").select("salary").to_a assert_equal 4, developers.size assert_equal 4, developers.map(&:salary).uniq.size end def test_finding_with_hash_conditions_on_joined_table - firms = DependentFirm.all.joins(:account).conditions({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a + firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_all_with_join - developers_on_project_one = Developer.all.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').conditions('project_id=1').to_a + developers_on_project_one = Developer.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). + where('project_id=1').to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } @@ -62,11 +126,11 @@ class RelationTest < ActiveRecord::TestCase end def test_find_on_hash_conditions - assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.all.conditions({ :approved => false }).to_a + assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a end def test_joins_with_string_array - person_with_reader_and_post = Post.all.joins([ + person_with_reader_and_post = Post.joins([ "INNER JOIN categorizations ON categorizations.post_id = posts.id", "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" ] @@ -74,8 +138,8 @@ class RelationTest < ActiveRecord::TestCase assert_equal 1, person_with_reader_and_post.size end - def test_relation_responds_to_delegated_methods - relation = Topic.all + def test_scoped_responds_to_delegated_methods + relation = Topic.scoped ["map", "uniq", "sort", "insert", "delete", "update"].each do |method| assert relation.respond_to?(method), "Topic.all should respond to #{method.inspect}" @@ -83,13 +147,14 @@ class RelationTest < ActiveRecord::TestCase end def test_find_with_readonly_option - Developer.all.each { |d| assert !d.readonly? } - Developer.all.readonly.each { |d| assert d.readonly? } - Developer.all(:readonly => true).each { |d| assert d.readonly? } + Developer.scoped.each { |d| assert !d.readonly? } + Developer.scoped.readonly.each { |d| assert d.readonly? } end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.all(:include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4').to_a + authors = Author.eager_load(:posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } }). + order('comments.body, very_special_comments_posts.body').where('posts.id = 4').to_a + assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments @@ -99,52 +164,138 @@ class RelationTest < ActiveRecord::TestCase def test_find_with_included_associations assert_queries(2) do - posts = Post.find(:all, :include => :comments) - posts.first.comments.first + posts = Post.preload(:comments) + assert posts.first.comments.first end + assert_queries(2) do - posts = Post.all(:include => :comments).to_a - posts.first.comments.first + posts = Post.preload(:comments).to_a + assert posts.first.comments.first end + assert_queries(2) do - posts = Post.find(:all, :include => :author) - posts.first.author + posts = Post.preload(:author) + assert posts.first.author end + assert_queries(2) do - posts = Post.all(:include => :author).to_a - posts.first.author + posts = Post.preload(:author).to_a + assert posts.first.author + end + + assert_queries(3) do + posts = Post.preload(:author, :comments).to_a + assert posts.first.author + assert posts.first.comments.first end end - def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.to_a.map(&:id).sort + def test_default_scope_with_conditions_string + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end - def test_loading_with_one_association - posts = Post.all(:include => :comments) + def test_default_scoping_finder_methods + developers = DeveloperCalledDavid.order('id').map(&:id).sort + assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers + end + + def test_loading_with_one_association + posts = Post.preload(:comments) post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'") + post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - posts = Post.all(:include => :last_comment) + posts = Post.preload(:last_comment) post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_with_one_association_with_non_preload - posts = Post.all(:include => :last_comment, :order => 'comments.id DESC') + posts = Post.eager_load(:last_comment).order('comments.id DESC') post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end -end + def test_dynamic_find_by_attributes + david = authors(:david) + author = Author.preload(:taggings).find_by_id(david.id) + expected_taggings = taggings(:welcome_general, :thinking_general) + + assert_no_queries do + assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } + end + + authors = Author.scoped + assert_equal david, authors.find_by_id_and_name(david.id, david.name) + assert_equal david, authors.find_by_id_and_name!(david.id, david.name) + end + + def test_dynamic_find_by_attributes_bang + author = Author.scoped.find_by_id!(authors(:david).id) + assert_equal "David", author.name + + assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!('invalid', 'wt') } + end + + def test_dynamic_find_all_by_attributes + authors = Author.scoped + + davids = authors.find_all_by_name('David') + assert_kind_of Array, davids + assert_equal [authors(:david)], davids + end + + def test_dynamic_find_or_initialize_by_attributes + authors = Author.scoped + + lifo = authors.find_or_initialize_by_name('Lifo') + assert_equal "Lifo", lifo.name + assert lifo.new_record? + + assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David') + end + + def test_dynamic_find_or_create_by_attributes + authors = Author.scoped + + lifo = authors.find_or_create_by_name('Lifo') + assert_equal "Lifo", lifo.name + assert ! lifo.new_record? + + assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') + end + + def test_find_id + authors = Author.scoped + + david = authors.find(authors(:david).id) + assert_equal 'David', david.name + + assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find('invalid') } + end + + def test_find_ids + authors = Author.order('id ASC') + + results = authors.find(authors(:david).id, authors(:mary).id) + assert_kind_of Array, results + assert_equal 2, results.size + assert_equal 'David', results[0].name + assert_equal 'Mary', results[1].name + assert_equal results, authors.find([authors(:david).id, authors(:mary).id]) + + assert_raises(ActiveRecord::RecordNotFound) { authors.where(:name => 'lifo').find(authors(:david).id, 'invalid') } + assert_raises(ActiveRecord::RecordNotFound) { authors.find(['invalid', 'oops']) } + end + +end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb new file mode 100644 index 0000000000..f221def6b6 --- /dev/null +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -0,0 +1,11 @@ +require "cases/helper" +require 'models/topic' + +class YamlSerializationTest < ActiveRecord::TestCase + def test_to_yaml_with_time_with_zone_should_not_raise_exception + Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] + ActiveRecord::Base.time_zone_aware_attributes = true + topic = Topic.new(:written_on => DateTime.now) + assert_nothing_raised { topic.to_yaml } + end +end diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 6566e84d4c..9fa1f86914 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -27,11 +27,8 @@ task :default => [ :test ] # Run the unit tests Rake::TestTask.new { |t| - activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib" - t.libs << activesupport_path if File.directory?(activesupport_path) t.libs << "test" t.pattern = 'test/**/*_test.rb' - t.verbose = true t.warning = true } diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb index fd4c199b48..3e4a1dd4a1 100644 --- a/activeresource/lib/active_resource.rb +++ b/activeresource/lib/active_resource.rb @@ -21,20 +21,24 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift(activesupport_path) if File.directory?(activesupport_path) -require 'active_support' +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) -activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib" -$:.unshift(activemodel_path) if File.directory?(activemodel_path) +require 'active_support' require 'active_model' module ActiveResource - autoload :Base, 'active_resource/base' - autoload :Connection, 'active_resource/connection' - autoload :CustomMethods, 'active_resource/custom_methods' - autoload :Formats, 'active_resource/formats' - autoload :Observing, 'active_resource/observing' - autoload :Validations, 'active_resource/validations' - autoload :HttpMock, 'active_resource/http_mock' + extend ActiveSupport::Autoload + + autoload :Base + autoload :Connection + autoload :CustomMethods + autoload :Formats + autoload :HttpMock + autoload :Observing + autoload :Schema + autoload :Validations end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 18105e8887..a6243e7011 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -13,6 +13,9 @@ require 'set' require 'uri' require 'active_resource/exceptions' +require 'active_resource/connection' +require 'active_resource/formats' +require 'active_resource/schema' module ActiveResource # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application. @@ -241,6 +244,124 @@ module ActiveResource cattr_accessor :logger class << self + # Creates a schema for this resource - setting the attributes that are + # known prior to fetching an instance from the remote system. + # + # The schema helps define the set of <tt>known_attributes</tt> of the + # current resource. + # + # There is no need to specify a schema for your Active Resource. If + # you do not, the <tt>known_attributes</tt> will be guessed from the + # instance attributes returned when an instance is fetched from the + # remote system. + # + # example: + # class Person < ActiveResource::Base + # schema do + # # define each attribute separately + # attribute 'name', :string + # + # # or use the convenience methods and pass >=1 attribute names + # string 'eye_colour', 'hair_colour' + # integer 'age' + # float 'height', 'weight' + # + # # unsupported types should be left as strings + # # overload the accessor methods if you need to convert them + # attribute 'created_at', 'string' + # end + # end + # + # p = Person.new + # p.respond_to? :name # => true + # p.respond_to? :age # => true + # p.name # => nil + # p.age # => nil + # + # j = Person.find_by_name('John') # <person><name>John</name><age>34</age><num_children>3</num_children></person> + # j.respond_to? :name # => true + # j.respond_to? :age # => true + # j.name # => 'John' + # j.age # => '34' # note this is a string! + # j.num_children # => '3' # note this is a string! + # + # p.num_children # => NoMethodError + # + # Attribute-types must be one of: + # string, integer, float + # + # Note: at present the attribute-type doesn't do anything, but stay + # tuned... + # Shortly it will also *cast* the value of the returned attribute. + # ie: + # j.age # => 34 # cast to an integer + # j.weight # => '65' # still a string! + # + def schema(&block) + if block_given? + schema_definition = Schema.new + schema_definition.instance_eval(&block) + + # skip out if we didn't define anything + return unless schema_definition.attrs.present? + + @schema ||= {}.with_indifferent_access + @known_attributes ||= [] + + schema_definition.attrs.each do |k,v| + @schema[k] = v + @known_attributes << k + end + + schema + else + @schema ||= nil + end + end + + # Alternative, direct way to specify a <tt>schema</tt> for this + # Resource. <tt>schema</tt> is more flexible, but this is quick + # for a very simple schema. + # + # Pass the schema as a hash with the keys being the attribute-names + # and the value being one of the accepted attribute types (as defined + # in <tt>schema</tt>) + # + # example: + # + # class Person < ActiveResource::Base + # schema = {'name' => :string, 'age' => :integer } + # end + # + # The keys/values can be strings or symbols. They will be converted to + # strings. + # + def schema=(the_schema) + unless the_schema.present? + # purposefully nulling out the schema + @schema = nil + @known_attributes = [] + return + end + + raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash + + schema do + the_schema.each {|k,v| attribute(k,v) } + end + end + + # Returns the list of known attributes for this resource, gathered + # from the provided <tt>schema</tt> + # Attributes that are known will cause your resource to return 'true' + # when <tt>respond_to?</tt> is called on them. A known attribute will + # return nil if not set (rather than <t>MethodNotFound</tt>); thus + # known attributes can be used with <tt>validates_presence_of</tt> + # without a getter-method. + def known_attributes + @known_attributes ||= [] + end + # Gets the URI of the REST resources to map for this class. The site variable is required for # Active Resource's mapping to work. def site @@ -776,6 +897,21 @@ module ActiveResource attr_accessor :attributes #:nodoc: attr_accessor :prefix_options #:nodoc: + # If no schema has been defined for the class (see + # <tt>ActiveResource::schema=</tt>), the default automatic schema is + # generated from the current instance's attributes + def schema + self.class.schema || self.attributes + end + + # This is a list of known attributes for this resource. Either + # gathered fromthe provided <tt>schema</tt>, or from the attributes + # set on this instance after it has been fetched from the remote system. + def known_attributes + self.class.known_attributes + self.attributes.keys.map(&:to_s) + end + + # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash # of attributes for the \new resource. # @@ -1157,7 +1293,7 @@ module ActiveResource method_name = method.to_s if attributes.nil? super - elsif attributes.has_key?(method_name) + elsif known_attributes.include?(method_name) true elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`) true @@ -1262,7 +1398,10 @@ module ActiveResource attributes[$`] end else - attributes.include?(method_name) ? attributes[method_name] : super + return attributes[method_name] if attributes.include?(method_name) + # not set right now but we know about it + return nil if known_attributes.include?(method_name) + super end end end diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb new file mode 100644 index 0000000000..8368b652c2 --- /dev/null +++ b/activeresource/lib/active_resource/schema.rb @@ -0,0 +1,55 @@ +require 'active_resource/exceptions' + +module ActiveResource # :nodoc: + class Schema # :nodoc: + # attributes can be known to be one of these types. They are easy to + # cast to/from. + KNOWN_ATTRIBUTE_TYPES = %w( string integer float ) + + # An array of attribute definitions, representing the attributes that + # have been defined. + attr_accessor :attrs + + # The internals of an Active Resource Schema are very simple - + # unlike an Active Record TableDefinition (on which it is based). + # It provides a set of convenience methods for people to define their + # schema using the syntax: + # schema do + # string :foo + # integer :bar + # end + # + # The schema stores the name and type of each attribute. That is then + # read out by the schema method to populate the actual + # Resource's schema + def initialize + @attrs = {} + end + + def attribute(name, type, options = {}) + raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s) + + the_type = type.to_s + # TODO: add defaults + #the_attr = [type.to_s] + #the_attr << options[:default] if options.has_key? :default + @attrs[name.to_s] = the_type + self + end + + # The following are the attribute types supported by Active Resource + # migrations. + # TODO: We should eventually support all of these: + # %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type| + KNOWN_ATTRIBUTE_TYPES.each do |attr_type| + class_eval <<-EOV + def #{attr_type.to_s}(*args) + options = args.extract_options! + attr_names = args + + attr_names.each { |name| attribute(name, '#{attr_type}', options) } + end + EOV + end + end +end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 10849be20c..5fa6d3023b 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,18 +1,17 @@ -root = File.expand_path('../../..', __FILE__) begin - require "#{root}/vendor/gems/environment" + require File.expand_path('../../../vendor/gems/environment', __FILE__) rescue LoadError - $:.unshift("#{root}/activesupport/lib") end -lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +lib = File.expand_path('../../lib', __FILE__) $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'rubygems' require 'test/unit' +require 'active_resource' require 'active_support' require 'active_support/test_case' -require 'active_resource' +require 'active_model/test_case' $:.unshift "#{File.dirname(__FILE__)}/../test" require 'setter_trap' diff --git a/activeresource/test/cases/base/schema_test.rb b/activeresource/test/cases/base/schema_test.rb new file mode 100644 index 0000000000..d9dc679941 --- /dev/null +++ b/activeresource/test/cases/base/schema_test.rb @@ -0,0 +1,420 @@ +require 'abstract_unit' +require 'active_support/core_ext/hash/conversions' +require "fixtures/person" +require "fixtures/street_address" + +######################################################################## +# Testing the schema of your Active Resource models +######################################################################## +class SchemaTest < ActiveModel::TestCase + def setup + @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') + @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') + @greg = { :id => 3, :name => 'Greg' }.to_xml(:root => 'person') + @addy = { :id => 1, :street => '12345 Street', :country => 'Australia' }.to_xml(:root => 'address') + @default_request_headers = { 'Content-Type' => 'application/xml' } + @rick = { :name => "Rick", :age => 25 }.to_xml(:root => "person") + @people = [{ :id => 1, :name => 'Matz' }, { :id => 2, :name => 'David' }].to_xml(:root => 'people') + @people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people') + @addresses = [{ :id => 1, :street => '12345 Street', :country => 'Australia' }].to_xml(:root => 'addresses') + + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/1.xml", {}, @matz + mock.get "/people/2.xml", {}, @david + mock.get "/people/Greg.xml", {}, @greg + mock.get "/people/4.xml", {'key' => 'value'}, nil, 404 + mock.get "/people/5.xml", {}, @rick + mock.put "/people/1.xml", {}, nil, 204 + mock.delete "/people/1.xml", {}, nil, 200 + mock.delete "/people/2.xml", {}, nil, 400 + mock.get "/people/99.xml", {}, nil, 404 + mock.post "/people.xml", {}, @rick, 201, 'Location' => '/people/5.xml' + mock.get "/people.xml", {}, @people + mock.get "/people/1/addresses.xml", {}, @addresses + mock.get "/people/1/addresses/1.xml", {}, @addy + mock.get "/people/1/addresses/2.xml", {}, nil, 404 + mock.get "/people/2/addresses/1.xml", {}, nil, 404 + mock.get "/people/Greg/addresses/1.xml", {}, @addy + mock.put "/people/1/addresses/1.xml", {}, nil, 204 + mock.delete "/people/1/addresses/1.xml", {}, nil, 200 + mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5' + mock.get "/people//addresses.xml", {}, nil, 404 + mock.get "/people//addresses/1.xml", {}, nil, 404 + mock.put "/people//addressaddresseses/1.xml", {}, nil, 404 + mock.delete "/people//addresses/1.xml", {}, nil, 404 + mock.post "/people//addresses.xml", {}, nil, 404 + mock.head "/people/1.xml", {}, nil, 200 + mock.head "/people/Greg.xml", {}, nil, 200 + mock.head "/people/99.xml", {}, nil, 404 + mock.head "/people/1/addresses/1.xml", {}, nil, 200 + mock.head "/people/1/addresses/2.xml", {}, nil, 404 + mock.head "/people/2/addresses/1.xml", {}, nil, 404 + mock.head "/people/Greg/addresses/1.xml", {}, nil, 200 + end + + Person.user = nil + Person.password = nil + end + def teardown + Person.schema = nil # hack to stop test bleedthrough... + end + + + ##################################################### + # Passing in a schema directly and returning it + #### + + test "schema on a new model should be empty" do + assert Person.schema.blank?, "should have a blank class schema" + assert Person.new.schema.blank?, "should have a blank instance schema" + end + + test "schema should only accept a hash" do + ["blahblah", ['one','two'], [:age, :name], Person.new].each do |bad_schema| + assert_raises(ArgumentError,"should only accept a hash (or nil), but accepted: #{bad_schema.inspect}") do + Person.schema = bad_schema + end + end + end + + test "schema should accept a simple hash" do + new_schema = {'age' => 'integer', 'name' => 'string'} + + assert_nothing_raised { Person.schema = new_schema } + assert_equal new_schema, Person.schema + end + + test "schema should accept a hash with simple values" do + new_schema = {'age' => 'integer', 'name' => 'string', 'height' => 'float', 'mydatetime' => 'string'} + + assert_nothing_raised { Person.schema = new_schema } + assert_equal new_schema, Person.schema + end + + test "schema should accept all known attribute types as values" do + # I'd prefer to use here... + ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type| + assert_nothing_raised("should have accepted #{the_type.inspect}"){ Person.schema = {'my_key' => the_type }} + end + end + + test "schema should not accept unknown values" do + bad_values = [ :oogle, :blob, 'thing'] + + bad_values.each do |bad_value| + assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do + Person.schema = {'key' => bad_value} + end + end + end + + test "schema should accept nil and remove the schema" do + new_schema = {'age' => 'integer', 'name' => 'string'} + assert_nothing_raised { Person.schema = new_schema } + assert_equal new_schema, Person.schema # sanity check + + + assert_nothing_raised { Person.schema = nil } + assert_nil Person.schema, "should have nulled out the schema, but still had: #{Person.schema.inspect}" + end + + + test "schema should be with indifferent access" do + new_schema = {:age => :integer, 'name' => 'string'} + new_schema_syms = new_schema.keys + + assert_nothing_raised { Person.schema = new_schema } + new_schema_syms.each do |col| + assert Person.new.respond_to?(col.to_s), "should respond to the schema's string key, but failed on: #{col.to_s}" + assert Person.new.respond_to?(col.to_sym), "should respond to the schema's symbol key, but failed on: #{col.to_sym}" + end + end + + + test "schema on a fetched resource should return all the attributes of that model instance" do + p = Person.find(1) + s = p.schema + + assert s.present?, "should have found a non-empty schema!" + + p.attributes.each do |the_attr, val| + assert s.has_key?(the_attr), "should have found attr: #{the_attr} in schema, but only had: #{s.inspect}" + end + end + + test "with two instances, default schema should match the attributes of the individual instances - even if they differ" do + matz = Person.find(1) + rick = Person.find(5) + + m_attrs = matz.attributes.keys.sort + r_attrs = rick.attributes.keys.sort + + assert_not_equal m_attrs, r_attrs, "should have different attributes on each model" + + assert_not_equal matz.schema, rick.schema, "should have had different schemas too" + end + + test "defining a schema should return it when asked" do + assert Person.schema.blank?, "should have a blank class schema" + new_schema = {'name' => 'string', 'age' => 'integer', 'height' => 'float', 'weight' => 'float'} + assert_nothing_raised { + Person.schema = new_schema + assert_equal new_schema, Person.schema, "should have saved the schema on the class" + assert_equal new_schema, Person.new.schema, "should have mde the schema available to every instance" + } + end + + test "defining a schema, then fetching a model should still match the defined schema" do + # sanity checks + assert Person.schema.blank?, "should have a blank class schema" + new_schema = {'name' => 'string', 'age' => 'integer', 'height' => 'float', 'weight' => 'float'} + + matz = Person.find(1) + assert !matz.schema.blank?, "should have some sort of schema on an instance variable" + assert_not_equal new_schema, matz.schema, "should not have the class-level schema until it's been added to the class!" + + assert_nothing_raised { + Person.schema = new_schema + assert_equal new_schema, matz.schema, "class-level schema should override instance-level schema" + } + end + + + ##################################################### + # Using the schema syntax + #### + + test "should be able to use schema" do + assert Person.respond_to?(:schema), "should at least respond to the schema method" + + assert_nothing_raised("Should allow the schema to take a block") do + Person.schema { } + end + end + + test "schema definition should store and return attribute set" do + assert_nothing_raised do + s = nil + Person.schema do + s = self + attribute :foo, :string + end + assert s.respond_to?(:attrs), "should return attributes in theory" + assert_equal({'foo' => 'string' }, s.attrs, "should return attributes in practice") + end + end + + test "should be able to add attributes through schema" do + assert_nothing_raised do + s = nil + Person.schema do + s = self + attribute('foo', 'string') + end + assert s.attrs.has_key?('foo'), "should have saved the attribute name" + assert_equal 'string', s.attrs['foo'], "should have saved the attribute type" + end + end + + test "should convert symbol attributes to strings" do + assert_nothing_raised do + s = nil + Person.schema do + attribute(:foo, :integer) + s = self + end + + assert s.attrs.has_key?('foo'), "should have saved the attribute name as a string" + assert_equal 'integer', s.attrs['foo'], "should have saved the attribute type as a string" + end + end + + test "should be able to add all known attribute types" do + assert_nothing_raised do + ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type| + s = nil + Person.schema do + s = self + attribute('foo', the_type) + end + assert s.attrs.has_key?('foo'), "should have saved the attribute name" + assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}" + end + end + end + + test "attributes should not accept unknown values" do + bad_values = [ :oogle, :blob, 'thing'] + + bad_values.each do |bad_value| + s = nil + assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do + Person.schema do + s = self + attribute 'key', bad_value + end + end + assert !self.respond_to?(bad_value), "should only respond to a known attribute type, but accepted: #{bad_value.inspect}" + assert_raises(NoMethodError,"should only have methods for known attribute types, but accepted: #{bad_value.inspect}") do + Person.schema do + send bad_value, 'key' + end + end + end + end + + + test "should accept attribute types as the type's name as the method" do + ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type| + s = nil + Person.schema do + s = self + send(the_type,'foo') + end + assert s.attrs.has_key?('foo'), "should now have saved the attribute name" + assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}" + end + end + + test "should accept multiple attribute names for an attribute method" do + names = ['foo','bar','baz'] + s = nil + Person.schema do + s = self + string(*names) + end + names.each do |the_name| + assert s.attrs.has_key?(the_name), "should now have saved the attribute name: #{the_name}" + assert_equal 'string', s.attrs[the_name], "should have saved the attribute as a string" + end + end + + ##################################################### + # What a schema does for us + #### + + # respond_to? + + test "should respond positively to attributes that are only in the schema" do + new_attr_name = :my_new_schema_attribute + new_attr_name_two = :another_new_schema_attribute + assert Person.schema.blank?, "sanity check - should have a blank class schema" + + assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" + + assert_nothing_raised do + Person.schema = {new_attr_name.to_s => 'string'} + Person.schema { string new_attr_name_two } + end + + assert Person.new.respond_to?(new_attr_name), "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}" + assert Person.new.respond_to?(new_attr_name_two), "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}" + end + + test "should not care about ordering of schema definitions" do + new_attr_name = :my_new_schema_attribute + new_attr_name_two = :another_new_schema_attribute + + assert Person.schema.blank?, "sanity check - should have a blank class schema" + + assert !Person.new.respond_do?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet" + assert !Person.new.respond_do?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet" + + assert_nothing_raised do + Person.schema { string new_attr_name_two } + Person.schema = {new_attr_name.to_s => 'string'} + end + + assert Person.new.respond_to?(new_attr_name), "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}" + assert Person.new.respond_to?(new_attr_name_two), "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}" + end + + # method_missing effects + + test "should not give method_missing for attribute only in schema" do + new_attr_name = :another_new_schema_attribute + new_attr_name_two = :another_new_schema_attribute + + assert Person.schema.blank?, "sanity check - should have a blank class schema" + + assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name} as a method") do + Person.new.send(new_attr_name) + end + assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name_two} as a method") do + Person.new.send(new_attr_name_two) + end + + Person.schema = {new_attr_name.to_s => :float} + Person.schema { string new_attr_name_two } + + assert_nothing_raised do + Person.new.send(new_attr_name) + Person.new.send(new_attr_name_two) + end + end + + + ######## + # Known attributes + # + # Attributes can be known to be attributes even if they aren't actually + # 'set' on a particular instance. + # This will only differ from 'attributes' if a schema has been set. + + test "new model should have no known attributes" do + assert Person.known_attributes.blank?, "should have no known attributes" + assert Person.new.known_attributes.blank?, "should have no known attributes on a new instance" + end + + test "setting schema should set known attributes on class and instance" do + new_schema = {'age' => 'integer', 'name' => 'string'} + + assert_nothing_raised { Person.schema = new_schema } + + assert_equal new_schema.keys, Person.known_attributes + assert_equal new_schema.keys, Person.new.known_attributes + end + + test "known attributes on a fetched resource should return all the attributes of the instance" do + p = Person.find(1) + attrs = p.known_attributes + + assert attrs.present?, "should have found some attributes!" + + p.attributes.each do |the_attr, val| + assert attrs.include?(the_attr), "should have found attr: #{the_attr} in known attributes, but only had: #{attrs.inspect}" + end + end + + test "with two instances, known attributes should match the attributes of the individual instances - even if they differ" do + matz = Person.find(1) + rick = Person.find(5) + + m_attrs = matz.attributes.keys.sort + r_attrs = rick.attributes.keys.sort + + assert_not_equal m_attrs, r_attrs, "should have different attributes on each model" + + assert_not_equal matz.known_attributes, rick.known_attributes, "should have had different known attributes too" + end + + test "setting schema then fetching should add schema attributes to the intance attributes" do + # an attribute in common with fetched instance and one that isn't + new_schema = {'name' => 'string', 'my_strange_attribute' => 'string'} + + assert_nothing_raised { Person.schema = new_schema } + + matz = Person.find(1) + known_attrs = matz.known_attributes + + matz.attributes.keys.each do |the_attr| + assert known_attrs.include?(the_attr), "should have found instance attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}" + end + new_schema.keys.each do |the_attr| + assert known_attrs.include?(the_attr), "should have found schema attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}" + end + end + + +end diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 1d3f7891ec..91349b810a 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -543,9 +543,9 @@ class BaseTest < Test::Unit::TestCase assert Person.collection_path(:gender => 'male', :student => true).include?('gender=male') assert Person.collection_path(:gender => 'male', :student => true).include?('student=true') - assert_equal '/people.xml?name%5B%5D=bob&name%5B%5D=your+uncle%2Bme&name%5B%5D=&name%5B%5D=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false]) + assert_equal '/people.xml?name[]=bob&name[]=your+uncle%2Bme&name[]=&name[]=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false]) - assert_equal '/people.xml?struct%5Ba%5D%5B%5D=2&struct%5Ba%5D%5B%5D=1&struct%5Bb%5D=fred', Person.collection_path(:struct => {:a => [2,1], 'b' => 'fred'}) + assert_equal '/people.xml?struct[a][]=2&struct[a][]=1&struct[b]=fred', Person.collection_path(:struct => {:a => [2,1], 'b' => 'fred'}) end def test_custom_element_path @@ -581,7 +581,7 @@ class BaseTest < Test::Unit::TestCase assert_equal '/people/1/addresses/1.xml?type=work', StreetAddress.element_path(1, :person_id => 1, :type => 'work') assert_equal '/people/1/addresses/1.xml?type=work', StreetAddress.element_path(1, 'person_id' => 1, :type => 'work') assert_equal '/people/1/addresses/1.xml?type=work', StreetAddress.element_path(1, :type => 'work', :person_id => 1) - assert_equal '/people/1/addresses/1.xml?type%5B%5D=work&type%5B%5D=play+time', StreetAddress.element_path(1, :person_id => 1, :type => ['work', 'play time']) + assert_equal '/people/1/addresses/1.xml?type[]=work&type[]=play+time', StreetAddress.element_path(1, :person_id => 1, :type => ['work', 'play time']) end def test_custom_element_path_with_prefix_and_parameters diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 4edeadf10c..cc4a2ff90e 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?/else calls [DHH] + * Update Edinburgh TimeZone to use "Europe/London" instead of "Europe/Dublin" #3310 [Phil Ross] * Update bundled TZInfo to v0.3.15 [Geoff Buesing] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 2ada91830f..08af1d6fca 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -18,7 +18,6 @@ task :default => :test Rake::TestTask.new do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' - t.verbose = true t.warning = true end diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 0478ae4ebc..f2baa5a56a 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,7 +34,41 @@ module ActiveSupport end end -require 'active_support/autoload' +require "active_support/dependencies/autoload" + +module ActiveSupport + extend ActiveSupport::Autoload + + # TODO: Narrow this list down + eager_autoload do + autoload :BacktraceCleaner + autoload :Base64 + autoload :BasicObject + autoload :Benchmarkable + autoload :BufferedLogger + autoload :Cache + autoload :Callbacks + autoload :Concern + autoload :Configurable + autoload :DeprecatedCallbacks + autoload :Deprecation + autoload :Gzip + autoload :Inflector + autoload :Memoizable + autoload :MessageEncryptor + autoload :MessageVerifier + autoload :Multibyte + autoload :OptionMerger + autoload :OrderedHash + autoload :OrderedOptions + autoload :Notifications + autoload :Rescuable + autoload :SecureRandom + autoload :StringInquirer + autoload :XmlMini + end +end + require 'active_support/vendor' require 'i18n' diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb deleted file mode 100644 index 63f7338a68..0000000000 --- a/activesupport/lib/active_support/autoload.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveSupport - autoload :BacktraceCleaner, 'active_support/backtrace_cleaner' - autoload :Base64, 'active_support/base64' - autoload :BasicObject, 'active_support/basic_object' - autoload :Benchmarkable, 'active_support/benchmarkable' - autoload :BufferedLogger, 'active_support/buffered_logger' - autoload :Cache, 'active_support/cache' - autoload :Callbacks, 'active_support/callbacks' - autoload :Concern, 'active_support/concern' - autoload :Configurable, 'active_support/configurable' - autoload :DependencyModule, 'active_support/dependency_module' - autoload :DeprecatedCallbacks, 'active_support/deprecated_callbacks' - autoload :Deprecation, 'active_support/deprecation' - autoload :Gzip, 'active_support/gzip' - autoload :Inflector, 'active_support/inflector' - autoload :Memoizable, 'active_support/memoizable' - autoload :MessageEncryptor, 'active_support/message_encryptor' - autoload :MessageVerifier, 'active_support/message_verifier' - autoload :Multibyte, 'active_support/multibyte' - autoload :OptionMerger, 'active_support/option_merger' - autoload :OrderedHash, 'active_support/ordered_hash' - autoload :OrderedOptions, 'active_support/ordered_options' - autoload :Notifications, 'active_support/notifications' - autoload :Rescuable, 'active_support/rescuable' - autoload :SecureRandom, 'active_support/secure_random' - autoload :StringInquirer, 'active_support/string_inquirer' - autoload :XmlMini, 'active_support/xml_mini' -end diff --git a/activesupport/lib/active_support/core_ext/class/removal.rb b/activesupport/lib/active_support/core_ext/class/removal.rb index 2dea3c24d5..652be4ed78 100644 --- a/activesupport/lib/active_support/core_ext/class/removal.rb +++ b/activesupport/lib/active_support/core_ext/class/removal.rb @@ -2,7 +2,11 @@ require 'active_support/core_ext/object/extending' require 'active_support/core_ext/module/introspection' class Class #:nodoc: - + + def reachable? + eval("defined?(::#{self}) && ::#{self}.equal?(self)") + end + # Unassociates the class with its subclasses and removes the subclasses # themselves. # diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 3dd61334d0..2b76b93153 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/duration' require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index f6c870035b..90ab1eb281 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/inflector' class Date diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 5f01bc4fd6..47a31839a6 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -78,7 +78,18 @@ class DateTime # Converts self to a floating-point number of seconds since the Unix epoch def to_f - days_since_unix_epoch = self - ::DateTime.civil(1970) - (days_since_unix_epoch * 86_400).to_f + seconds_since_unix_epoch.to_f + end + + # Converts self to an integer number of seconds since the Unix epoch + def to_i + seconds_since_unix_epoch.to_i + end + + private + + def seconds_since_unix_epoch + seconds_per_day = 86_400 + (self - ::DateTime.civil(1970)) * seconds_per_day end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index b11c916f61..d0821a7c68 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -101,6 +101,11 @@ module Enumerable size = block_given? ? select(&block).size : self.size size > 1 end + + # The negative of the Enumerable#include?. Returns true if the collection does not include the object. + def exclude?(object) + !include?(object) + end end class Range #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index e4df8fe338..22749229a3 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -137,10 +137,10 @@ class Logger attr_writer :formatter public :formatter= - alias old_format_datetime format_datetime if method_defined?(:format_datetime) + alias old_format_datetime format_datetime def format_datetime(datetime) datetime end - alias old_msg2str msg2str if method_defined?(:msg2str) + alias old_msg2str msg2str def msg2str(msg) msg end end end diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index 0cc74c8298..de8121f274 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -1,46 +1,53 @@ -class Object - def remove_subclasses_of(*superclasses) #:nodoc: - Class.remove_class(*subclasses_of(*superclasses)) - end - - begin - ObjectSpace.each_object(Class.new) {} - - # Exclude this class unless it's a subclass of our supers and is defined. - # We check defined? in case we find a removed class that has yet to be - # garbage collected. This also fails for anonymous classes -- please - # submit a patch if you have a workaround. - def subclasses_of(*superclasses) #:nodoc: +class Class + # Rubinius + if defined?(Class.__subclasses__) + def descendents subclasses = [] - - superclasses.each do |sup| - ObjectSpace.each_object(class << sup; self; end) do |k| - if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k - end - end - end - + __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents } subclasses end - rescue RuntimeError - # JRuby and any implementations which cannot handle the objectspace traversal - # above fall back to this implementation - def subclasses_of(*superclasses) #:nodoc: - subclasses = [] + else + # MRI + begin + ObjectSpace.each_object(Class.new) {} - superclasses.each do |sup| + def descendents + subclasses = [] + ObjectSpace.each_object(class << self; self; end) do |k| + subclasses << k unless k == self + end + subclasses + end + # JRuby + rescue StandardError + def descendents + subclasses = [] ObjectSpace.each_object(Class) do |k| - if superclasses.any? { |superclass| k < superclass } && - (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k - end + subclasses << k if k < self end subclasses.uniq! + subclasses end - subclasses end end +end + +class Object + def remove_subclasses_of(*superclasses) #:nodoc: + Class.remove_class(*subclasses_of(*superclasses)) + end + + # Exclude this class unless it's a subclass of our supers and is defined. + # We check defined? in case we find a removed class that has yet to be + # garbage collected. This also fails for anonymous classes -- please + # submit a patch if you have a workaround. + def subclasses_of(*superclasses) #:nodoc: + subclasses = [] + superclasses.each do |klass| + subclasses.concat klass.descendents.select {|k| k.name.blank? || k.reachable?} + end + subclasses + end def extended_by #:nodoc: ancestors = class << self; ancestors end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index a5e2260791..7ca763cbad 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -38,7 +38,7 @@ class Hash # ==== Examples # { :name => 'David', :nationality => 'Danish' }.to_query # => "name=David&nationality=Danish" # - # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user[name]=David&user[nationality]=Danish" def to_param(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 3f1540f685..c9981895b4 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -7,7 +7,7 @@ class Object # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. def to_query(key) require 'cgi' unless defined?(CGI) && defined?(CGI::escape) - "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" + "#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(to_param.to_s)}" end end @@ -15,7 +15,7 @@ class Array # Converts an array into a string suitable for use as a URL query string, # using the given +key+ as the param name. # - # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies[]=Rails&hobbies[]=coding" def to_query(key) prefix = "#{key}[]" collect { |value| value.to_query(prefix) }.join '&' 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 2cca4763f4..3e6ab0ebd2 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,43 +1,29 @@ class String - def html_safe? - defined?(@_rails_html_safe) && @_rails_html_safe - end + attr_accessor :_rails_html_safe + alias html_safe? _rails_html_safe def html_safe! @_rails_html_safe = true self end - + def html_safe dup.html_safe! end - + alias original_plus + def +(other) result = original_plus(other) - if html_safe? && also_html_safe?(other) - result.html_safe! - else - result - end + result._rails_html_safe = html_safe? && other.html_safe? + result end - + alias original_concat << + alias safe_concat << def <<(other) + @_rails_html_safe = false unless other.html_safe? result = original_concat(other) - unless html_safe? && also_html_safe?(other) - @_rails_html_safe = false - end - result end - - def concat(other) - self << other - end - - private - def also_html_safe?(other) - other.respond_to?(:html_safe?) && other.html_safe? - end - + + alias concat << end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 4f4492f0fd..703b89ffd0 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,4 +1,5 @@ require 'active_support/duration' +require 'active_support/core_ext/date/acts_like' require 'active_support/core_ext/date/calculations' class Time diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb new file mode 100644 index 0000000000..44edb89ad5 --- /dev/null +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,49 @@ +require "active_support/inflector/methods" + +module ActiveSupport + module Autoload + @@autoloads = {} + @@under_path = nil + @@at_path = nil + @@eager_autoload = false + + def autoload(const_name, path = @@at_path) + full = [self.name, @@under_path, const_name.to_s, path].compact.join("::") + location = path || Inflector.underscore(full) + + if @@eager_autoload + @@autoloads[const_name] = location + end + super const_name, location + end + + def autoload_under(path) + @@under_path, old_path = path, @@under_path + yield + ensure + @@under_path = old_path + end + + def autoload_at(path) + @@at_path, old_path = path, @@at_path + yield + ensure + @@at_path = old_path + end + + def eager_autoload + old_eager, @@eager_autoload = @@eager_autoload, true + yield + ensure + @@eager_autoload = old_eager + end + + def self.eager_autoload! + @@autoloads.values.each { |file| require file } + end + + def autoloads + @@autoloads + end + end +end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 3c15056c41..c8415d5449 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -65,6 +65,15 @@ module ActiveSupport ESCAPED_CHARS = { + "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', + "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005', + "\x06" => '\u0006', "\x07" => '\u0007', "\x0B" => '\u000B', + "\x0E" => '\u000E', "\x0F" => '\u000F', "\x10" => '\u0010', + "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013', + "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', + "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', + "\x1A" => '\u001A', "\x1B" => '\u001B', "\x1C" => '\u001C', + "\x1D" => '\u001D', "\x1E" => '\u001E', "\x1F" => '\u001F', "\010" => '\b', "\f" => '\f', "\n" => '\n', @@ -86,9 +95,9 @@ module ActiveSupport def escape_html_entities_in_json=(value) self.escape_regex = \ if @escape_html_entities_in_json = value - /[\010\f\n\r\t"\\><&]/ + /[\x00-\x1F"\\><&]/ else - /[\010\f\n\r\t"\\]/ + /[\x00-\x1F"\\]/ end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index e2540cd598..d9bfcbfcab 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,7 +1,4 @@ -require 'thread' require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/secure_random' module ActiveSupport # Notifications provides an instrumentation API for Ruby. To instrument an @@ -41,173 +38,42 @@ module ActiveSupport # to subscribers in a thread. You can use any queue implementation you want. # module Notifications - mattr_accessor :queue, :listener + autoload :Instrumenter, 'active_support/notifications/instrumenter' + autoload :Event, 'active_support/notifications/instrumenter' + autoload :Fanout, 'active_support/notifications/fanout' class << self - delegate :instrument, :transaction_id, :transaction, :to => :instrumenter + attr_writer :notifier + delegate :publish, :subscribe, :instrument, :to => :notifier - def instrumenter - Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher) - end - - def publisher - @publisher ||= Publisher.new(queue) - end - - def subscriber - @subscriber ||= Subscriber.new(queue) - end - - def subscribe(pattern=nil, options={}, &block) - with = options[:with] || listener - subscriber.bind(with, pattern).subscribe(&block) + def notifier + @notifier ||= Notifier.new end end - class Instrumenter - def initialize(publisher) - @publisher = publisher - @id = random_id - end - - def transaction - @id, old_id = random_id, @id - yield - ensure - @id = old_id - end - - def transaction_id - @id - end - - def instrument(name, payload={}) - time = Time.now - result = yield if block_given? - ensure - @publisher.publish(name, time, Time.now, result, @id, payload) - end - - private - def random_id - SecureRandom.hex(10) - end - end - - class Publisher - def initialize(queue) + class Notifier + def initialize(queue = Fanout.new) @queue = queue end def publish(*args) @queue.publish(*args) end - end - - class Subscriber - def initialize(queue) - @queue = queue - end - - def bind(listener, pattern) - @listener = listener - @pattern = pattern - self - end - def subscribe - @queue.subscribe(@listener, @pattern) do |*args| - yield(*args) - end + def subscribe(pattern = nil, &block) + @queue.bind(pattern).subscribe(&block) end - end - - class Event - attr_reader :name, :time, :end, :transaction_id, :result, :payload - def initialize(name, start, ending, result, transaction_id, payload) - @name = name - @payload = payload.dup - @time = start - @transaction_id = transaction_id - @end = ending - @result = result + def wait + @queue.wait end - def duration - @duration ||= 1000.0 * (@end - @time) - end + delegate :instrument, :to => :current_instrumenter - def parent_of?(event) - start = (self.time - event.time) * 1000 - start <= 0 && (start + duration >= event.duration) - end - end - - class AsyncListener - def initialize(pattern, &block) - @pattern = pattern - @subscriber = block - @queue = Queue.new - Thread.new { consume } - end - - def publish(name, *args) - if !@pattern || @pattern === name.to_s - @queue << args.unshift(name) - end - end - - def consume - while args = @queue.shift - @subscriber.call(*args) - end - end - - def drained? - @queue.size.zero? - end - end - - class SyncListener - def initialize(pattern, &block) - @pattern = pattern - @subscriber = block - end - - def publish(name, *args) - if !@pattern || @pattern === name.to_s - @subscriber.call(*args.unshift(name)) + private + def current_instrumenter + Thread.current[:"instrumentation_#{object_id}"] ||= Notifications::Instrumenter.new(self) end - end - - def drained? - true - end - end - - # This is a default queue implementation that ships with Notifications. It - # consumes events in a thread and publish them to all registered subscribers. - # - class LittleFanout - def initialize - @listeners = [] - end - - def publish(*args) - @listeners.each { |l| l.publish(*args) } - end - - def subscribe(listener, pattern=nil, &block) - @listeners << listener.new(pattern, &block) - end - - def drained? - @listeners.all? &:drained? - end end end - - Notifications.queue = Notifications::LittleFanout.new - Notifications.listener = Notifications::AsyncListener end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb new file mode 100644 index 0000000000..bb07e4765c --- /dev/null +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -0,0 +1,101 @@ +require 'thread' + +module ActiveSupport + module Notifications + # This is a default queue implementation that ships with Notifications. It + # consumes events in a thread and publish them to all registered subscribers. + # + class Fanout + def initialize(sync = false) + @subscriber_klass = sync ? Subscriber : AsyncSubscriber + @subscribers = [] + end + + def bind(pattern) + Binding.new(self, pattern) + end + + def subscribe(pattern = nil, &block) + @subscribers << @subscriber_klass.new(pattern, &block) + end + + def publish(*args) + @subscribers.each { |s| s.publish(*args) } + end + + def wait + sleep(0.05) until @subscribers.all?(&:drained?) + end + + # Used for internal implementation only. + class Binding #:nodoc: + def initialize(queue, pattern) + @queue = queue + @pattern = + case pattern + when Regexp, NilClass + pattern + else + /^#{Regexp.escape(pattern.to_s)}/ + end + end + + def subscribe(&block) + @queue.subscribe(@pattern, &block) + end + end + + class Subscriber #:nodoc: + def initialize(pattern, &block) + @pattern = pattern + @block = block + end + + def publish(*args) + push(*args) if matches?(args.first) + end + + def drained? + true + end + + private + def matches?(name) + !@pattern || @pattern =~ name.to_s + end + + def push(*args) + @block.call(*args) + end + end + + # Used for internal implementation only. + class AsyncSubscriber < Subscriber #:nodoc: + def initialize(pattern, &block) + super + @events = Queue.new + start_consumer + end + + def drained? + @events.empty? + end + + private + def start_consumer + Thread.new { consume } + end + + def consume + while args = @events.shift + @block.call(*args) + end + end + + def push(*args) + @events << args + end + end + end + end +end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 0000000000..fb95422af2 --- /dev/null +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,47 @@ +require 'active_support/secure_random' +require 'active_support/core_ext/module/delegation' + +module ActiveSupport + module Notifications + class Instrumenter + def initialize(notifier) + @id = unique_id + @notifier = notifier + end + + def instrument(name, payload={}) + time = Time.now + result = yield if block_given? + ensure + @notifier.publish(name, time, Time.now, result, @id, payload) + end + + private + def unique_id + SecureRandom.hex(10) + end + end + + class Event + attr_reader :name, :time, :end, :transaction_id, :result, :payload + + def initialize(name, start, ending, result, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @result = result + end + + def duration + @duration ||= 1000.0 * (@end - @time) + end + + def parent_of?(event) + start = (self.time - event.time) * 1000 + start <= 0 && (start + duration >= event.duration) + end + end + end +end diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index f811239077..1e49ccdade 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -14,5 +14,6 @@ require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/process/daemon' 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' diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index c75b59c284..81f7e3c9df 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,10 +1,25 @@ module ActiveSupport module Testing + class RemoteError < StandardError + + attr_reader :message, :backtrace + + def initialize(exception) + @message = "caught #{exception.class.name}: #{exception.message}" + @backtrace = exception.backtrace + end + end + class ProxyTestResult def initialize @calls = [] end + def add_error(e) + e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception)) + @calls << [:add_error, e] + end + def __replay__(result) @calls.each do |name, args| result.send(name, *args) diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index ab34f975f6..66e32fa5d7 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -1,450 +1,454 @@ -require 'ruby-prof' - -require 'fileutils' -require 'rails/version' - -module ActiveSupport - module Testing - module Performance - DEFAULTS = - if benchmark = ARGV.include?('--benchmark') # HAX for rake test - { :benchmark => true, - :runs => 4, - :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time], - :output => 'tmp/performance' } - else - { :benchmark => false, - :runs => 1, - :min_percent => 0.01, - :metrics => [:process_time, :memory, :objects], - :formats => [:flat, :graph_html, :call_tree], - :output => 'tmp/performance' } - end.freeze - - def self.included(base) - base.superclass_delegating_accessor :profile_options - base.profile_options = DEFAULTS - end +begin + require 'ruby-prof' + + require 'fileutils' + require 'rails/version' + require 'active_support/core_ext/class/delegating_attributes' + + module ActiveSupport + module Testing + module Performance + DEFAULTS = + if benchmark = ARGV.include?('--benchmark') # HAX for rake test + { :benchmark => true, + :runs => 4, + :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time], + :output => 'tmp/performance' } + else + { :benchmark => false, + :runs => 1, + :min_percent => 0.01, + :metrics => [:process_time, :memory, :objects], + :formats => [:flat, :graph_html, :call_tree], + :output => 'tmp/performance' } + end.freeze + + def self.included(base) + base.superclass_delegating_accessor :profile_options + base.profile_options = DEFAULTS + end - def full_test_name - "#{self.class.name}##{method_name}" - end + def full_test_name + "#{self.class.name}##{method_name}" + end - def run(result) - return if method_name =~ /^default_test$/ + def run(result) + return if method_name =~ /^default_test$/ - yield(self.class::STARTED, name) - @_result = result + yield(self.class::STARTED, name) + @_result = result - run_warmup - if profile_options && metrics = profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - result.add_run + run_warmup + if profile_options && metrics = profile_options[:metrics] + metrics.each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) + result.add_run + end end end - end - yield(self.class::FINISHED, name) - end + yield(self.class::FINISHED, name) + end - def run_test(metric, mode) - run_callbacks :setup - setup - metric.send(mode) { __send__ @method_name } - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError - add_error($!) - ensure - begin - teardown - run_callbacks :teardown, :enumerator => :reverse_each + def run_test(metric, mode) + run_callbacks :setup + setup + metric.send(mode) { __send__ @method_name } rescue ::Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) rescue StandardError, ScriptError add_error($!) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + end end - end - protected - def run_warmup - GC.start + protected + def run_warmup + GC.start - time = Metrics::Time.new - run_test(time, :benchmark) - puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] + time = Metrics::Time.new + run_test(time, :benchmark) + puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] - GC.start - end - - def run_profile(metric) - klass = profile_options[:benchmark] ? Benchmarker : Profiler - performer = klass.new(self, metric) + GC.start + end - performer.run - puts performer.report - performer.record - end + def run_profile(metric) + klass = profile_options[:benchmark] ? Benchmarker : Profiler + performer = klass.new(self, metric) - class Performer - delegate :run_test, :profile_options, :full_test_name, :to => :@harness + performer.run + puts performer.report + performer.record + end - def initialize(harness, metric) - @harness, @metric = harness, metric - end + class Performer + delegate :run_test, :profile_options, :full_test_name, :to => :@harness - def report - rate = @total / profile_options[:runs] - '%20s: %s' % [@metric.name, @metric.format(rate)] - end + def initialize(harness, metric) + @harness, @metric = harness, metric + end - protected - def output_filename - "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}" + def report + rate = @total / profile_options[:runs] + '%20s: %s' % [@metric.name, @metric.format(rate)] end - end - class Benchmarker < Performer - def run - profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } - @total = @metric.total + protected + def output_filename + "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}" + end end - def record - avg = @metric.total / profile_options[:runs].to_i - now = Time.now.utc.xmlschema - with_output_file do |file| - file.puts "#{avg},#{now},#{environment}" + class Benchmarker < Performer + def run + profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } + @total = @metric.total end - end - def environment - unless defined? @env - app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - - rails = Rails::VERSION::STRING - if File.directory?('vendor/rails/.git') - Dir.chdir('vendor/rails') do - rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - end + def record + avg = @metric.total / profile_options[:runs].to_i + now = Time.now.utc.xmlschema + with_output_file do |file| + file.puts "#{avg},#{now},#{environment}" end - - ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' - ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - - @env = [app, rails, ruby, RUBY_PLATFORM] * ',' end - @env - end + def environment + unless defined? @env + app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - protected - HEADER = 'measurement,created_at,app,rails,ruby,platform' + rails = Rails::VERSION::STRING + if File.directory?('vendor/rails/.git') + Dir.chdir('vendor/rails') do + rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + end + end - def with_output_file - fname = output_filename + ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' + ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - if new = !File.exist?(fname) - FileUtils.mkdir_p(File.dirname(fname)) + @env = [app, rails, ruby, RUBY_PLATFORM] * ',' end - File.open(fname, 'ab') do |file| - file.puts(HEADER) if new - yield file - end + @env end - def output_filename - "#{super}.csv" - end - end + protected + HEADER = 'measurement,created_at,app,rails,ruby,platform' - class Profiler < Performer - def initialize(*args) - super - @supported = @metric.measure_mode rescue false - end + def with_output_file + fname = output_filename + + if new = !File.exist?(fname) + FileUtils.mkdir_p(File.dirname(fname)) + end - def run - return unless @supported + File.open(fname, 'ab') do |file| + file.puts(HEADER) if new + yield file + end + end - RubyProf.measure_mode = @metric.measure_mode - RubyProf.start - RubyProf.pause - profile_options[:runs].to_i.times { run_test(@metric, :profile) } - @data = RubyProf.stop - @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } + def output_filename + "#{super}.csv" + end end - def report - if @supported + class Profiler < Performer + def initialize(*args) super - else - '%20s: unsupported' % @metric.name + @supported = @metric.measure_mode rescue false end - end - def record - return unless @supported + def run + return unless @supported - klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact + RubyProf.measure_mode = @metric.measure_mode + RubyProf.start + RubyProf.pause + profile_options[:runs].to_i.times { run_test(@metric, :profile) } + @data = RubyProf.stop + @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } + end - klasses.each do |klass| - fname = output_filename(klass) - FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, 'wb') do |file| - klass.new(@data).print(file, profile_options.slice(:min_percent)) + def report + if @supported + super + else + '%20s: unsupported' % @metric.name end end - end - protected - def output_filename(printer_class) - suffix = - case printer_class.name.demodulize - when 'FlatPrinter'; 'flat.txt' - when 'GraphPrinter'; 'graph.txt' - when 'GraphHtmlPrinter'; 'graph.html' - when 'CallTreePrinter'; 'tree.txt' - else printer_class.name.sub(/Printer$/, '').underscore - end + def record + return unless @supported + + klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact - "#{super()}_#{suffix}" + klasses.each do |klass| + fname = output_filename(klass) + FileUtils.mkdir_p(File.dirname(fname)) + File.open(fname, 'wb') do |file| + klass.new(@data).print(file, profile_options.slice(:min_percent)) + end + end end - end - module Metrics - def self.[](name) - const_get(name.to_s.camelize) - rescue NameError - nil + protected + def output_filename(printer_class) + suffix = + case printer_class.name.demodulize + when 'FlatPrinter'; 'flat.txt' + when 'GraphPrinter'; 'graph.txt' + when 'GraphHtmlPrinter'; 'graph.html' + when 'CallTreePrinter'; 'tree.txt' + else printer_class.name.sub(/Printer$/, '').underscore + end + + "#{super()}_#{suffix}" + end end - class Base - attr_reader :total - - def initialize - @total = 0 + module Metrics + def self.[](name) + const_get(name.to_s.camelize) + rescue NameError + nil end - def name - @name ||= self.class.name.demodulize.underscore - end + class Base + attr_reader :total - def measure_mode - self.class::Mode - end + def initialize + @total = 0 + end - def measure - 0 - end + def name + @name ||= self.class.name.demodulize.underscore + end - def benchmark - with_gc_stats do - before = measure - yield - @total += (measure - before) + def measure_mode + self.class::Mode end - end - def profile - RubyProf.resume - yield - ensure - RubyProf.pause - end + def measure + 0 + end - protected - if GC.respond_to?(:enable_stats) - def with_gc_stats - GC.enable_stats - yield - ensure - GC.disable_stats - end - elsif defined?(GC::Profiler) - def with_gc_stats - GC.start - GC.disable - GC::Profiler.enable - yield - ensure - GC::Profiler.disable - GC.enable - end - else - def with_gc_stats + def benchmark + with_gc_stats do + before = measure yield + @total += (measure - before) end end - end - class Time < Base - def measure - ::Time.now.to_f - end - - def format(measurement) - if measurement < 2 - '%d ms' % (measurement * 1000) - else - '%.2f sec' % measurement + def profile + RubyProf.resume + yield + ensure + RubyProf.pause end - end - end - - class ProcessTime < Time - Mode = RubyProf::PROCESS_TIME - - def measure - RubyProf.measure_process_time - end - end - - class WallTime < Time - Mode = RubyProf::WALL_TIME - def measure - RubyProf.measure_wall_time + protected + if GC.respond_to?(:enable_stats) + def with_gc_stats + GC.enable_stats + yield + ensure + GC.disable_stats + end + elsif defined?(GC::Profiler) + def with_gc_stats + GC.start + GC.disable + GC::Profiler.enable + yield + ensure + GC::Profiler.disable + GC.enable + end + else + def with_gc_stats + yield + end + end end - end - class CpuTime < Time - Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) - - def initialize(*args) - # FIXME: yeah my CPU is 2.33 GHz - RubyProf.cpu_frequency = 2.33e9 - super - end + class Time < Base + def measure + ::Time.now.to_f + end - def measure - RubyProf.measure_cpu_time + def format(measurement) + if measurement < 2 + '%d ms' % (measurement * 1000) + else + '%.2f sec' % measurement + end + end end - end - class Memory < Base - Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) + class ProcessTime < Time + Mode = RubyProf::PROCESS_TIME - # ruby-prof wrapper - if RubyProf.respond_to?(:measure_memory) def measure - RubyProf.measure_memory / 1024.0 + RubyProf.measure_process_time end + end - # Ruby 1.8 + railsbench patch - elsif GC.respond_to?(:allocated_size) - def measure - GC.allocated_size / 1024.0 - end + class WallTime < Time + Mode = RubyProf::WALL_TIME - # Ruby 1.8 + lloyd patch - elsif GC.respond_to?(:heap_info) def measure - GC.heap_info['heap_current_memory'] / 1024.0 + RubyProf.measure_wall_time end + end - # Ruby 1.9 with total_malloc_allocated_size patch - elsif GC.respond_to?(:malloc_total_allocated_size) - def measure - GC.total_malloc_allocated_size / 1024.0 - end + class CpuTime < Time + Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) - # Ruby 1.9 unpatched - elsif GC.respond_to?(:malloc_allocated_size) - def measure - GC.malloc_allocated_size / 1024.0 + def initialize(*args) + # FIXME: yeah my CPU is 2.33 GHz + RubyProf.cpu_frequency = 2.33e9 + super end - # Ruby 1.9 + GC profiler patch - elsif defined?(GC::Profiler) def measure - GC.enable - GC.start - kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0 - GC.disable - kb + RubyProf.measure_cpu_time end end - def format(measurement) - '%.2f KB' % measurement - end - end + class Memory < Base + Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) - class Objects < Base - Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) + # ruby-prof wrapper + if RubyProf.respond_to?(:measure_memory) + def measure + RubyProf.measure_memory / 1024.0 + end - if RubyProf.respond_to?(:measure_allocations) - def measure - RubyProf.measure_allocations - end + # Ruby 1.8 + railsbench patch + elsif GC.respond_to?(:allocated_size) + def measure + GC.allocated_size / 1024.0 + end - # Ruby 1.8 + railsbench patch - elsif ObjectSpace.respond_to?(:allocated_objects) - def measure - ObjectSpace.allocated_objects + # Ruby 1.8 + lloyd patch + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['heap_current_memory'] / 1024.0 + end + + # Ruby 1.9 with total_malloc_allocated_size patch + elsif GC.respond_to?(:malloc_total_allocated_size) + def measure + GC.total_malloc_allocated_size / 1024.0 + end + + # Ruby 1.9 unpatched + elsif GC.respond_to?(:malloc_allocated_size) + def measure + GC.malloc_allocated_size / 1024.0 + end + + # Ruby 1.9 + GC profiler patch + elsif defined?(GC::Profiler) + def measure + GC.enable + GC.start + kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0 + GC.disable + kb + end end - # Ruby 1.9 + GC profiler patch - elsif defined?(GC::Profiler) - def measure - GC.enable - GC.start - last = GC::Profiler.data.last - count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS] - GC.disable - count + def format(measurement) + '%.2f KB' % measurement end end - def format(measurement) - measurement.to_i.to_s - end - end + class Objects < Base + Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) - class GcRuns < Base - Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) + if RubyProf.respond_to?(:measure_allocations) + def measure + RubyProf.measure_allocations + end - if RubyProf.respond_to?(:measure_gc_runs) - def measure - RubyProf.measure_gc_runs - end - elsif GC.respond_to?(:collections) - def measure - GC.collections - end - elsif GC.respond_to?(:heap_info) - def measure - GC.heap_info['num_gc_passes'] + # Ruby 1.8 + railsbench patch + elsif ObjectSpace.respond_to?(:allocated_objects) + def measure + ObjectSpace.allocated_objects + end + + # Ruby 1.9 + GC profiler patch + elsif defined?(GC::Profiler) + def measure + GC.enable + GC.start + last = GC::Profiler.data.last + count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS] + GC.disable + count + end end - end - def format(measurement) - measurement.to_i.to_s + def format(measurement) + measurement.to_i.to_s + end end - end - class GcTime < Base - Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + class GcRuns < Base + Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) - if RubyProf.respond_to?(:measure_gc_time) - def measure - RubyProf.measure_gc_time + if RubyProf.respond_to?(:measure_gc_runs) + def measure + RubyProf.measure_gc_runs + end + elsif GC.respond_to?(:collections) + def measure + GC.collections + end + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['num_gc_passes'] + end end - elsif GC.respond_to?(:time) - def measure - GC.time + + def format(measurement) + measurement.to_i.to_s end end - def format(measurement) - '%d ms' % (measurement / 1000) + class GcTime < Base + Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + + if RubyProf.respond_to?(:measure_gc_time) + def measure + RubyProf.measure_gc_time + end + elsif GC.respond_to?(:time) + def measure + GC.time + end + end + + def format(measurement) + '%d ms' % (measurement / 1000) + end end end end end end -end +rescue LoadError +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index eb5080888c..1e46491d83 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -4,7 +4,7 @@ def ActiveSupport.requirable?(file) $LOAD_PATH.any? { |p| Dir.glob("#{p}/#{file}.*").any? } end -[%w(builder 2.1.2), %w(i18n 0.1.3), %w(memcache-client 1.7.5), %w(tzinfo 0.3.15)].each do |lib, version| +[%w(builder 2.1.2), %w(memcache-client 1.7.5), %w(tzinfo 0.3.15)].each do |lib, version| # If the lib is not already requirable unless ActiveSupport.requirable? lib # Try to activate a gem ~> satisfying the requested version first. diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE b/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE deleted file mode 100755 index ed8e9ee66d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008 The Ruby I18n team - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile b/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile deleted file mode 100644 index a07fc8426d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile +++ /dev/null @@ -1,20 +0,0 @@ -h1. Ruby I18n gem - -I18n and localization solution for Ruby. - -For information please refer to http://rails-i18n.org - -h2. Authors - -* "Matt Aimonetti":http://railsontherun.com -* "Sven Fuchs":http://www.artweb-design.de -* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey -* "Saimon Moore":http://saimonmoore.net -* "Stephan Soller":http://www.arkanis-development.de - -h2. License - -MIT License. See the included MIT-LICENCE file. - - - diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile b/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile deleted file mode 100644 index 2164e13e69..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile +++ /dev/null @@ -1,5 +0,0 @@ -task :default => [:test] - -task :test do - ruby "test/all.rb" -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec b/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec deleted file mode 100644 index f102689a6f..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -Gem::Specification.new do |s| - s.name = "i18n" - s.version = "0.1.3" - s.date = "2009-01-09" - s.summary = "Internationalization support for Ruby" - s.email = "rails-i18n@googlegroups.com" - s.homepage = "http://rails-i18n.org" - s.description = "Add Internationalization support to your Ruby application." - s.has_rdoc = false - s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore'] - s.files = [ - 'i18n.gemspec', - 'lib/i18n/backend/simple.rb', - 'lib/i18n/exceptions.rb', - 'lib/i18n.rb', - 'MIT-LICENSE', - 'README.textile' - ] - s.test_files = [ - 'test/all.rb', - 'test/i18n_exceptions_test.rb', - 'test/i18n_test.rb', - 'test/locale/en.rb', - 'test/locale/en.yml', - 'test/simple_backend_test.rb' - ] -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb deleted file mode 100755 index 1b49debc05..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb +++ /dev/null @@ -1,204 +0,0 @@ -#-- -# Authors:: Matt Aimonetti (http://railsontherun.com/), -# Sven Fuchs (http://www.artweb-design.de), -# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey), -# Saimon Moore (http://saimonmoore.net), -# Stephan Soller (http://www.arkanis-development.de/) -# Copyright:: Copyright (c) 2008 The Ruby i18n Team -# License:: MIT -#++ - -module I18n - autoload :ArgumentError, 'i18n/exceptions' - module Backend - autoload :Simple, 'i18n/backend/simple' - end - - @@backend = nil - @@load_path = nil - @@default_locale = :'en' - @@exception_handler = :default_exception_handler - - class << self - # Returns the current backend. Defaults to +Backend::Simple+. - def backend - @@backend ||= Backend::Simple.new - end - - # Sets the current backend. Used to set a custom backend. - def backend=(backend) - @@backend = backend - end - - # Returns the current default locale. Defaults to :'en' - def default_locale - @@default_locale - end - - # Sets the current default locale. Used to set a custom default locale. - def default_locale=(locale) - @@default_locale = locale - end - - # Returns the current locale. Defaults to I18n.default_locale. - def locale - Thread.current[:locale] ||= default_locale - end - - # Sets the current locale pseudo-globally, i.e. in the Thread.current hash. - def locale=(locale) - Thread.current[:locale] = locale - end - - # Returns an array of locales for which translations are available - def available_locales - backend.available_locales - end - - # Sets the exception handler. - def exception_handler=(exception_handler) - @@exception_handler = exception_handler - end - - # Allow clients to register paths providing translation data sources. The - # backend defines acceptable sources. - # - # E.g. the provided SimpleBackend accepts a list of paths to translation - # files which are either named *.rb and contain plain Ruby Hashes or are - # named *.yml and contain YAML data. So for the SimpleBackend clients may - # register translation files like this: - # I18n.load_path << 'path/to/locale/en.yml' - def load_path - @@load_path ||= [] - end - - # Sets the load path instance. Custom implementations are expected to - # behave like a Ruby Array. - def load_path=(load_path) - @@load_path = load_path - end - - # Tells the backend to reload translations. Used in situations like the - # Rails development environment. Backends can implement whatever strategy - # is useful. - def reload! - backend.reload! - end - - # Translates, pluralizes and interpolates a given key using a given locale, - # scope, and default, as well as interpolation values. - # - # *LOOKUP* - # - # Translation data is organized as a nested hash using the upper-level keys - # as namespaces. <em>E.g.</em>, ActionView ships with the translation: - # <tt>:date => {:formats => {:short => "%b %d"}}</tt>. - # - # Translations can be looked up at any level of this hash using the key argument - # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt> - # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>. - # - # Key can be either a single key or a dot-separated key (both Strings and Symbols - # work). <em>E.g.</em>, the short format can be looked up using both: - # I18n.t 'date.formats.short' - # I18n.t :'date.formats.short' - # - # Scope can be either a single key, a dot-separated key or an array of keys - # or dot-separated keys. Keys and scopes can be combined freely. So these - # examples will all look up the same short date format: - # I18n.t 'date.formats.short' - # I18n.t 'formats.short', :scope => 'date' - # I18n.t 'short', :scope => 'date.formats' - # I18n.t 'short', :scope => %w(date formats) - # - # *INTERPOLATION* - # - # Translations can contain interpolation variables which will be replaced by - # values passed to #translate as part of the options hash, with the keys matching - # the interpolation variable names. - # - # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option - # value for the key +bar+ will be interpolated into the translation: - # I18n.t :foo, :bar => 'baz' # => 'foo baz' - # - # *PLURALIZATION* - # - # Translation data can contain pluralized translations. Pluralized translations - # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>. - # - # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English - # pluralization rules. Other algorithms can be supported by custom backends. - # - # This returns the singular version of a pluralized translation: - # I18n.t :foo, :count => 1 # => 'Foo' - # - # These both return the plural version of a pluralized translation: - # I18n.t :foo, :count => 0 # => 'Foos' - # I18n.t :foo, :count => 2 # => 'Foos' - # - # The <tt>:count</tt> option can be used both for pluralization and interpolation. - # <em>E.g.</em>, with the translation - # <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will - # be interpolated to the pluralized translation: - # I18n.t :foo, :count => 1 # => '1 foo' - # - # *DEFAULTS* - # - # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found: - # I18n.t :foo, :default => 'default' - # - # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no - # translation for <tt>:foo</tt> was found: - # I18n.t :foo, :default => :bar - # - # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> - # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found. - # I18n.t :foo, :default => [:bar, 'default'] - # - # <b>BULK LOOKUP</b> - # - # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>. - # I18n.t [:foo, :bar] - # - # Can be used with dot-separated nested keys: - # I18n.t [:'baz.foo', :'baz.bar'] - # - # Which is the same as using a scope option: - # I18n.t [:foo, :bar], :scope => :baz - def translate(key, options = {}) - locale = options.delete(:locale) || I18n.locale - backend.translate(locale, key, options) - rescue I18n::ArgumentError => e - raise e if options[:raise] - send(@@exception_handler, e, locale, key, options) - end - alias :t :translate - - # Localizes certain objects, such as dates and numbers to local formatting. - def localize(object, options = {}) - locale = options[:locale] || I18n.locale - format = options[:format] || :default - backend.localize(locale, object, format) - end - alias :l :localize - - protected - # Handles exceptions raised in the backend. All exceptions except for - # MissingTranslationData exceptions are re-raised. When a MissingTranslationData - # was caught and the option :raise is not set the handler returns an error - # message string containing the key/scope. - def default_exception_handler(exception, locale, key, options) - return exception.message if MissingTranslationData === exception - raise exception - end - - # Merges the given locale, key and scope into a single array of keys. - # Splits keys that contain dots into multiple keys. Makes sure all - # keys are Symbols. - def normalize_translation_keys(locale, key, scope) - keys = [locale] + Array(scope) + [key] - keys = keys.map { |k| k.to_s.split(/\./) } - keys.flatten.map { |k| k.to_sym } - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb deleted file mode 100644 index c32cc76f34..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb +++ /dev/null @@ -1,215 +0,0 @@ -require 'i18n/exceptions' - -module I18n - module Backend - class Simple - INTERPOLATION_RESERVED_KEYS = %w(scope default) - MATCH = /(\\\\)?\{\{([^\}]+)\}\}/ - - # Accepts a list of paths to translation files. Loads translations from - # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml - # for details. - def load_translations(*filenames) - filenames.each { |filename| load_file(filename) } - end - - # Stores translations for the given locale in memory. - # This uses a deep merge for the translations hash, so existing - # translations will be overwritten by new ones only at the deepest - # level of the hash. - def store_translations(locale, data) - merge_translations(locale, data) - end - - def translate(locale, key, options = {}) - raise InvalidLocale.new(locale) if locale.nil? - return key.map { |k| translate(locale, k, options) } if key.is_a? Array - - reserved = :scope, :default - count, scope, default = options.values_at(:count, *reserved) - options.delete(:default) - values = options.reject { |name, value| reserved.include?(name) } - - entry = lookup(locale, key, scope) - if entry.nil? - entry = default(locale, default, options) - if entry.nil? - raise(I18n::MissingTranslationData.new(locale, key, options)) - end - end - entry = pluralize(locale, entry, count) - entry = interpolate(locale, entry, values) - entry - end - - # Acts the same as +strftime+, but returns a localized version of the - # formatted date string. Takes a key from the date/time formats - # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>). - def localize(locale, object, format = :default) - raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) - - type = object.respond_to?(:sec) ? 'time' : 'date' - # TODO only translate these if format is a String? - formats = translate(locale, :"#{type}.formats") - format = formats[format.to_sym] if formats && formats[format.to_sym] - # TODO raise exception unless format found? - format = format.to_s.dup - - # TODO only translate these if the format string is actually present - # TODO check which format strings are present, then bulk translate then, then replace them - format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday]) - format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday]) - format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon]) - format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon]) - format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour - object.strftime(format) - end - - def initialized? - @initialized ||= false - end - - # Returns an array of locales for which translations are available - def available_locales - init_translations unless initialized? - translations.keys - end - - def reload! - @initialized = false - @translations = nil - end - - protected - def init_translations - load_translations(*I18n.load_path.flatten) - @initialized = true - end - - def translations - @translations ||= {} - end - - # Looks up a translation from the translations hash. Returns nil if - # eiher key is nil, or locale, scope or key do not exist as a key in the - # nested translations hash. Splits keys or scopes containing dots - # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as - # <tt>%w(currency format)</tt>. - def lookup(locale, key, scope = []) - return unless key - init_translations unless initialized? - keys = I18n.send(:normalize_translation_keys, locale, key, scope) - keys.inject(translations) do |result, k| - if (x = result[k.to_sym]).nil? - return nil - else - x - end - end - end - - # Evaluates a default translation. - # If the given default is a String it is used literally. If it is a Symbol - # it will be translated with the given options. If it is an Array the first - # translation yielded will be returned. - # - # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if - # <tt>translate(locale, :foo)</tt> does not yield a result. - def default(locale, default, options = {}) - case default - when String then default - when Symbol then translate locale, default, options - when Array then default.each do |obj| - result = default(locale, obj, options.dup) and return result - end and nil - end - rescue MissingTranslationData - nil - end - - # Picks a translation from an array according to English pluralization - # rules. It will pick the first translation if count is not equal to 1 - # and the second translation if it is equal to 1. Other backends can - # implement more flexible or complex pluralization rules. - def pluralize(locale, entry, count) - return entry unless entry.is_a?(Hash) and count - # raise InvalidPluralizationData.new(entry, count) unless entry.is_a?(Hash) - key = :zero if count == 0 && entry.has_key?(:zero) - key ||= count == 1 ? :one : :other - raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key) - entry[key] - end - - # Interpolates values into a given string. - # - # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X' - # # => "file test.txt opened by {{user}}" - # - # Note that you have to double escape the <tt>\\</tt> when you want to escape - # the <tt>{{...}}</tt> key in a string (once for the string and once for the - # interpolation). - def interpolate(locale, string, values = {}) - return string unless string.is_a?(String) - - string.gsub(MATCH) do - escaped, pattern, key = $1, $2, $2.to_sym - - if escaped - pattern - elsif INTERPOLATION_RESERVED_KEYS.include?(pattern) - raise ReservedInterpolationKey.new(pattern, string) - elsif !values.include?(key) - raise MissingInterpolationArgument.new(pattern, string) - else - values[key].to_s - end - end - end - - # Loads a single translations file by delegating to #load_rb or - # #load_yml depending on the file extension and directly merges the - # data to the existing translations. Raises I18n::UnknownFileType - # for all other file extensions. - def load_file(filename) - type = File.extname(filename).tr('.', '').downcase - raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}") - data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash - data.each { |locale, d| merge_translations(locale, d) } - end - - # Loads a plain Ruby translations file. eval'ing the file must yield - # a Hash containing translation data with locales as toplevel keys. - def load_rb(filename) - eval(IO.read(filename), binding, filename) - end - - # Loads a YAML translations file. The data must have locales as - # toplevel keys. - def load_yml(filename) - require 'yaml' unless defined? :YAML - YAML::load(IO.read(filename)) - end - - # Deep merges the given translations hash with the existing translations - # for the given locale - def merge_translations(locale, data) - locale = locale.to_sym - translations[locale] ||= {} - data = deep_symbolize_keys(data) - - # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 - merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } - translations[locale].merge!(data, &merger) - end - - # Return a new hash with all keys and nested keys converted to symbols. - def deep_symbolize_keys(hash) - hash.inject({}) { |result, (key, value)| - value = deep_symbolize_keys(value) if value.is_a? Hash - result[(key.to_sym rescue key) || key] = value - result - } - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb deleted file mode 100644 index 6897055d6d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb +++ /dev/null @@ -1,53 +0,0 @@ -module I18n - class ArgumentError < ::ArgumentError; end - - class InvalidLocale < ArgumentError - attr_reader :locale - def initialize(locale) - @locale = locale - super "#{locale.inspect} is not a valid locale" - end - end - - class MissingTranslationData < ArgumentError - attr_reader :locale, :key, :options - def initialize(locale, key, options) - @key, @locale, @options = key, locale, options - keys = I18n.send(:normalize_translation_keys, locale, key, options[:scope]) - keys << 'no key' if keys.size < 2 - super "translation missing: #{keys.join(', ')}" - end - end - - class InvalidPluralizationData < ArgumentError - attr_reader :entry, :count - def initialize(entry, count) - @entry, @count = entry, count - super "translation data #{entry.inspect} can not be used with :count => #{count}" - end - end - - class MissingInterpolationArgument < ArgumentError - attr_reader :key, :string - def initialize(key, string) - @key, @string = key, string - super "interpolation argument #{key} missing in #{string.inspect}" - end - end - - class ReservedInterpolationKey < ArgumentError - attr_reader :key, :string - def initialize(key, string) - @key, @string = key, string - super "reserved key #{key.inspect} used in #{string.inspect}" - end - end - - class UnknownFileType < ArgumentError - attr_reader :type, :filename - def initialize(type, filename) - @type, @filename = type, filename - super "can not load translations from #{filename}, the file type #{type} is not known" - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb deleted file mode 100644 index 353712da49..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb +++ /dev/null @@ -1,5 +0,0 @@ -dir = File.dirname(__FILE__) -require dir + '/i18n_test.rb' -require dir + '/simple_backend_test.rb' -require dir + '/i18n_exceptions_test.rb' -# *require* dir + '/custom_backend_test.rb'
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb deleted file mode 100644 index 4e78e71b34..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'active_support' - -class I18nExceptionsTest < Test::Unit::TestCase - def test_invalid_locale_stores_locale - force_invalid_locale - rescue I18n::ArgumentError => e - assert_nil e.locale - end - - def test_invalid_locale_message - force_invalid_locale - rescue I18n::ArgumentError => e - assert_equal 'nil is not a valid locale', e.message - end - - def test_missing_translation_data_stores_locale_key_and_options - force_missing_translation_data - rescue I18n::ArgumentError => e - options = {:scope => :bar} - assert_equal 'de', e.locale - assert_equal :foo, e.key - assert_equal options, e.options - end - - def test_missing_translation_data_message - force_missing_translation_data - rescue I18n::ArgumentError => e - assert_equal 'translation missing: de, bar, foo', e.message - end - - def test_invalid_pluralization_data_stores_entry_and_count - force_invalid_pluralization_data - rescue I18n::ArgumentError => e - assert_equal [:bar], e.entry - assert_equal 1, e.count - end - - def test_invalid_pluralization_data_message - force_invalid_pluralization_data - rescue I18n::ArgumentError => e - assert_equal 'translation data [:bar] can not be used with :count => 1', e.message - end - - def test_missing_interpolation_argument_stores_key_and_string - force_missing_interpolation_argument - rescue I18n::ArgumentError => e - assert_equal 'bar', e.key - assert_equal "{{bar}}", e.string - end - - def test_missing_interpolation_argument_message - force_missing_interpolation_argument - rescue I18n::ArgumentError => e - assert_equal 'interpolation argument bar missing in "{{bar}}"', e.message - end - - def test_reserved_interpolation_key_stores_key_and_string - force_reserved_interpolation_key - rescue I18n::ArgumentError => e - assert_equal 'scope', e.key - assert_equal "{{scope}}", e.string - end - - def test_reserved_interpolation_key_message - force_reserved_interpolation_key - rescue I18n::ArgumentError => e - assert_equal 'reserved key "scope" used in "{{scope}}"', e.message - end - - private - def force_invalid_locale - I18n.backend.translate nil, :foo - end - - def force_missing_translation_data - I18n.backend.store_translations 'de', :bar => nil - I18n.backend.translate 'de', :foo, :scope => :bar - end - - def force_invalid_pluralization_data - I18n.backend.store_translations 'de', :foo => [:bar] - I18n.backend.translate 'de', :foo, :count => 1 - end - - def force_missing_interpolation_argument - I18n.backend.store_translations 'de', :foo => "{{bar}}" - I18n.backend.translate 'de', :foo, :baz => 'baz' - end - - def force_reserved_interpolation_key - I18n.backend.store_translations 'de', :foo => "{{scope}}" - I18n.backend.translate 'de', :foo, :baz => 'baz' - end -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb deleted file mode 100644 index 2835ec4eab..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'active_support' - -class I18nTest < Test::Unit::TestCase - def setup - I18n.backend.store_translations :'en', { - :currency => { - :format => { - :separator => '.', - :delimiter => ',', - } - } - } - end - - def test_uses_simple_backend_set_by_default - assert I18n.backend.is_a?(I18n::Backend::Simple) - end - - def test_can_set_backend - assert_nothing_raised{ I18n.backend = self } - assert_equal self, I18n.backend - I18n.backend = I18n::Backend::Simple.new - end - - def test_uses_en_us_as_default_locale_by_default - assert_equal 'en', I18n.default_locale - end - - def test_can_set_default_locale - assert_nothing_raised{ I18n.default_locale = 'de' } - assert_equal 'de', I18n.default_locale - I18n.default_locale = 'en' - end - - def test_uses_default_locale_as_locale_by_default - assert_equal I18n.default_locale, I18n.locale - end - - def test_can_set_locale_to_thread_current - assert_nothing_raised{ I18n.locale = 'de' } - assert_equal 'de', I18n.locale - assert_equal 'de', Thread.current[:locale] - I18n.locale = 'en' - end - - def test_can_set_exception_handler - assert_nothing_raised{ I18n.exception_handler = :custom_exception_handler } - I18n.exception_handler = :default_exception_handler # revert it - end - - def test_uses_custom_exception_handler - I18n.exception_handler = :custom_exception_handler - I18n.expects(:custom_exception_handler) - I18n.translate :bogus - I18n.exception_handler = :default_exception_handler # revert it - end - - def test_delegates_translate_to_backend - I18n.backend.expects(:translate).with 'de', :foo, {} - I18n.translate :foo, :locale => 'de' - end - - def test_delegates_localize_to_backend - I18n.backend.expects(:localize).with 'de', :whatever, :default - I18n.localize :whatever, :locale => 'de' - end - - def test_translate_given_no_locale_uses_i18n_locale - I18n.backend.expects(:translate).with 'en', :foo, {} - I18n.translate :foo - end - - def test_translate_on_nested_symbol_keys_works - assert_equal ".", I18n.t(:'currency.format.separator') - end - - def test_translate_with_nested_string_keys_works - assert_equal ".", I18n.t('currency.format.separator') - end - - def test_translate_with_array_as_scope_works - assert_equal ".", I18n.t(:separator, :scope => ['currency.format']) - end - - def test_translate_with_array_containing_dot_separated_strings_as_scope_works - assert_equal ".", I18n.t(:separator, :scope => ['currency.format']) - end - - def test_translate_with_key_array_and_dot_separated_scope_works - assert_equal [".", ","], I18n.t(%w(separator delimiter), :scope => 'currency.format') - end - - def test_translate_with_dot_separated_key_array_and_scope_works - assert_equal [".", ","], I18n.t(%w(format.separator format.delimiter), :scope => 'currency') - end - - def test_translate_with_options_using_scope_works - I18n.backend.expects(:translate).with('de', :precision, :scope => :"currency.format") - I18n.with_options :locale => 'de', :scope => :'currency.format' do |locale| - locale.t :precision - end - end - - # def test_translate_given_no_args_raises_missing_translation_data - # assert_equal "translation missing: en, no key", I18n.t - # end - - def test_translate_given_a_bogus_key_raises_missing_translation_data - assert_equal "translation missing: en, bogus", I18n.t(:bogus) - end - - def test_localize_nil_raises_argument_error - assert_raise(I18n::ArgumentError) { I18n.l nil } - end - - def test_localize_object_raises_argument_error - assert_raise(I18n::ArgumentError) { I18n.l Object.new } - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb deleted file mode 100644 index 6044ce10d9..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb +++ /dev/null @@ -1 +0,0 @@ -{:'en-Ruby' => {:foo => {:bar => "baz"}}}
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml deleted file mode 100644 index 0b298c9c0e..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml +++ /dev/null @@ -1,3 +0,0 @@ -en-Yaml: - foo: - bar: baz
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb deleted file mode 100644 index a1696c77f6..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +++ /dev/null @@ -1,567 +0,0 @@ -# encoding: utf-8 -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'time' -require 'yaml' - -module I18nSimpleBackendTestSetup - def setup_backend - # backend_reset_translations! - @backend = I18n::Backend::Simple.new - @backend.store_translations 'en', :foo => {:bar => 'bar', :baz => 'baz'} - @locale_dir = File.dirname(__FILE__) + '/locale' - end - alias :setup :setup_backend - - # def backend_reset_translations! - # I18n::Backend::Simple::ClassMethods.send :class_variable_set, :@@translations, {} - # end - - def backend_get_translations - # I18n::Backend::Simple::ClassMethods.send :class_variable_get, :@@translations - @backend.instance_variable_get :@translations - end - - def add_datetime_translations - @backend.store_translations :'de', { - :date => { - :formats => { - :default => "%d.%m.%Y", - :short => "%d. %b", - :long => "%d. %B %Y", - }, - :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag), - :abbr_day_names => %w(So Mo Di Mi Do Fr Sa), - :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil), - :abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil), - :order => [:day, :month, :year] - }, - :time => { - :formats => { - :default => "%a, %d. %b %Y %H:%M:%S %z", - :short => "%d. %b %H:%M", - :long => "%d. %B %Y %H:%M", - }, - :am => 'am', - :pm => 'pm' - }, - :datetime => { - :distance_in_words => { - :half_a_minute => 'half a minute', - :less_than_x_seconds => { - :one => 'less than 1 second', - :other => 'less than {{count}} seconds' - }, - :x_seconds => { - :one => '1 second', - :other => '{{count}} seconds' - }, - :less_than_x_minutes => { - :one => 'less than a minute', - :other => 'less than {{count}} minutes' - }, - :x_minutes => { - :one => '1 minute', - :other => '{{count}} minutes' - }, - :about_x_hours => { - :one => 'about 1 hour', - :other => 'about {{count}} hours' - }, - :x_days => { - :one => '1 day', - :other => '{{count}} days' - }, - :about_x_months => { - :one => 'about 1 month', - :other => 'about {{count}} months' - }, - :x_months => { - :one => '1 month', - :other => '{{count}} months' - }, - :about_x_years => { - :one => 'about 1 year', - :other => 'about {{count}} year' - }, - :over_x_years => { - :one => 'over 1 year', - :other => 'over {{count}} years' - } - } - } - } - end -end - -class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_store_translations_adds_translations # no, really :-) - @backend.store_translations :'en', :foo => 'bar' - assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations - end - - def test_store_translations_deep_merges_translations - @backend.store_translations :'en', :foo => {:bar => 'bar'} - @backend.store_translations :'en', :foo => {:baz => 'baz'} - assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations - end - - def test_store_translations_forces_locale_to_sym - @backend.store_translations 'en', :foo => 'bar' - assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations - end - - def test_store_translations_converts_keys_to_symbols - # backend_reset_translations! - @backend.store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'} - assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations - end -end - -class I18nSimpleBackendAvailableLocalesTest < Test::Unit::TestCase - def test_available_locales - @backend = I18n::Backend::Simple.new - @backend.store_translations 'de', :foo => 'bar' - @backend.store_translations 'en', :foo => 'foo' - - assert_equal ['de', 'en'], @backend.available_locales.map{|locale| locale.to_s }.sort - end -end - -class I18nSimpleBackendTranslateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_translate_calls_lookup_with_locale_given - @backend.expects(:lookup).with('de', :bar, [:foo]).returns 'bar' - @backend.translate 'de', :bar, :scope => [:foo] - end - - def test_given_no_keys_it_returns_the_default - assert_equal 'default', @backend.translate('en', nil, :default => 'default') - end - - def test_translate_given_a_symbol_as_a_default_translates_the_symbol - assert_equal 'bar', @backend.translate('en', nil, :scope => [:foo], :default => :bar) - end - - def test_translate_given_an_array_as_default_uses_the_first_match - assert_equal 'bar', @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar]) - end - - def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data - assert_raise I18n::MissingTranslationData do - @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3]) - end - end - - def test_translate_an_array_of_keys_translates_all_of_them - assert_equal %w(bar baz), @backend.translate('en', [:bar, :baz], :scope => [:foo]) - end - - def test_translate_calls_pluralize - @backend.expects(:pluralize).with 'en', 'bar', 1 - @backend.translate 'en', :bar, :scope => [:foo], :count => 1 - end - - def test_translate_calls_interpolate - @backend.expects(:interpolate).with 'en', 'bar', {} - @backend.translate 'en', :bar, :scope => [:foo] - end - - def test_translate_calls_interpolate_including_count_as_a_value - @backend.expects(:interpolate).with 'en', 'bar', {:count => 1} - @backend.translate 'en', :bar, :scope => [:foo], :count => 1 - end - - def test_translate_given_nil_as_a_locale_raises_an_argument_error - assert_raise(I18n::InvalidLocale){ @backend.translate nil, :bar } - end - - def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data - assert_raise(I18n::MissingTranslationData){ @backend.translate 'de', :bogus } - end -end - -class I18nSimpleBackendLookupTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - # useful because this way we can use the backend with no key for interpolation/pluralization - def test_lookup_given_nil_as_a_key_returns_nil - assert_nil @backend.send(:lookup, 'en', nil) - end - - def test_lookup_given_nested_keys_looks_up_a_nested_hash_value - assert_equal 'bar', @backend.send(:lookup, 'en', :bar, [:foo]) - end -end - -class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_pluralize_given_nil_returns_the_given_entry - entry = {:one => 'bar', :other => 'bars'} - assert_equal entry, @backend.send(:pluralize, nil, entry, nil) - end - - def test_pluralize_given_0_returns_zero_string_if_zero_key_given - assert_equal 'zero', @backend.send(:pluralize, nil, {:zero => 'zero', :one => 'bar', :other => 'bars'}, 0) - end - - def test_pluralize_given_0_returns_plural_string_if_no_zero_key_given - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 0) - end - - def test_pluralize_given_1_returns_singular_string - assert_equal 'bar', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 1) - end - - def test_pluralize_given_2_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 2) - end - - def test_pluralize_given_3_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 3) - end - - def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data - assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) } - end - - # def test_interpolate_given_a_string_raises_invalid_pluralization_data - # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) } - # end - # - # def test_interpolate_given_an_array_raises_invalid_pluralization_data - # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) } - # end -end - -class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_interpolate_given_a_value_hash_interpolates_the_values_to_the_string - assert_equal 'Hi David!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'David') - end - - def test_interpolate_given_a_value_hash_interpolates_into_unicode_string - assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David') - end - - def test_interpolate_given_an_unicode_value_hash_interpolates_to_the_string - assert_equal 'Hi ゆきひろ!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'ゆきひろ') - end - - def test_interpolate_given_an_unicode_value_hash_interpolates_into_unicode_string - assert_equal 'こんにちは、ゆきひろさん!', @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => 'ゆきひろ') - end - - if Kernel.const_defined?(:Encoding) - def test_interpolate_given_a_non_unicode_multibyte_value_hash_interpolates_into_a_string_with_the_same_encoding - assert_equal euc_jp('Hi ゆきひろ!'), @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => euc_jp('ゆきひろ')) - end - - def test_interpolate_given_an_unicode_value_hash_into_a_non_unicode_multibyte_string_raises_encoding_compatibility_error - assert_raise(Encoding::CompatibilityError) do - @backend.send(:interpolate, nil, euc_jp('こんにちは、{{name}}さん!'), :name => 'ゆきひろ') - end - end - - def test_interpolate_given_a_non_unicode_multibyte_value_hash_into_an_unicode_string_raises_encoding_compatibility_error - assert_raise(Encoding::CompatibilityError) do - @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => euc_jp('ゆきひろ')) - end - end - end - - def test_interpolate_given_nil_as_a_string_returns_nil - assert_nil @backend.send(:interpolate, nil, nil, :name => 'David') - end - - def test_interpolate_given_an_non_string_as_a_string_returns_nil - assert_equal [], @backend.send(:interpolate, nil, [], :name => 'David') - end - - def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string - assert_equal 'Hi !', @backend.send(:interpolate, nil, 'Hi {{name}}!', {:name => nil}) - end - - def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument - assert_raise(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) } - end - - def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key - assert_raise(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) } - end - - private - - def euc_jp(string) - string.encode!(Encoding::EUC_JP) - end -end - -class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - add_datetime_translations - @date = Date.new 2008, 1, 1 - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan', @backend.localize('de', @date, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008', @backend.localize('de', @date, :long) - end - - def test_translate_given_the_default_format_it_uses_it - assert_equal '01.01.2008', @backend.localize('de', @date, :default) - end - - def test_translate_given_a_day_name_format_it_returns_a_day_name - assert_equal 'Dienstag', @backend.localize('de', @date, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_an_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @date, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_a_month_name - assert_equal 'Januar', @backend.localize('de', @date, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_an_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @date, '%b') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @date } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @date, '%x' } - end - - def test_localize_nil_raises_argument_error - assert_raise(I18n::ArgumentError) { @backend.localize 'de', nil } - end - - def test_localize_object_raises_argument_error - assert_raise(I18n::ArgumentError) { @backend.localize 'de', Object.new } - end -end - -class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - add_datetime_translations - @morning = DateTime.new 2008, 1, 1, 6 - @evening = DateTime.new 2008, 1, 1, 18 - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long) - end - - def test_translate_given_the_default_format_it_uses_it - assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default) - end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de', @morning, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de', @morning, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de', @morning, '%p') - assert_equal 'pm', @backend.localize('de', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning, '%x' } - end -end - -class I18nSimpleBackendLocalizeTimeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @old_timezone, ENV['TZ'] = ENV['TZ'], 'UTC' - @backend = I18n::Backend::Simple.new - add_datetime_translations - @morning = Time.parse '2008-01-01 6:00 UTC' - @evening = Time.parse '2008-01-01 18:00 UTC' - end - - def teardown - @old_timezone ? ENV['TZ'] = @old_timezone : ENV.delete('TZ') - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long) - end - - # TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this? - # def test_translate_given_the_default_format_it_uses_it - # assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default) - # end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de', @morning, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de', @morning, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de', @morning, '%p') - assert_equal 'pm', @backend.localize('de', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning, '%x' } - end -end - -class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase - def setup - @backend = I18n::Backend::Simple.new - end - - def test_deep_symbolize_keys_works - result = @backend.send :deep_symbolize_keys, 'foo' => {'bar' => {'baz' => 'bar'}} - expected = {:foo => {:bar => {:baz => 'bar'}}} - assert_equal expected, result - end -end - -class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_load_translations_with_unknown_file_type_raises_exception - assert_raise(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" } - end - - def test_load_translations_with_ruby_file_type_does_not_raise_exception - assert_nothing_raised { @backend.load_translations "#{@locale_dir}/en.rb" } - end - - def test_load_rb_loads_data_from_ruby_file - data = @backend.send :load_rb, "#{@locale_dir}/en.rb" - assert_equal({:'en-Ruby' => {:foo => {:bar => "baz"}}}, data) - end - - def test_load_rb_loads_data_from_yaml_file - data = @backend.send :load_yml, "#{@locale_dir}/en.yml" - assert_equal({'en-Yaml' => {'foo' => {'bar' => 'baz'}}}, data) - end - - def test_load_translations_loads_from_different_file_formats - @backend = I18n::Backend::Simple.new - @backend.load_translations "#{@locale_dir}/en.rb", "#{@locale_dir}/en.yml" - expected = { - :'en-Ruby' => {:foo => {:bar => "baz"}}, - :'en-Yaml' => {:foo => {:bar => "baz"}} - } - assert_equal expected, backend_get_translations - end -end - -class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def teardown - I18n.load_path = [] - end - - def test_nested_load_paths_do_not_break_locale_loading - @backend = I18n::Backend::Simple.new - I18n.load_path = [[File.dirname(__FILE__) + '/locale/en.yml']] - assert_nil backend_get_translations - assert_nothing_raised { @backend.send :init_translations } - assert_not_nil backend_get_translations - end - - def test_adding_arrays_of_filenames_to_load_path_do_not_break_locale_loading - @backend = I18n::Backend::Simple.new - I18n.load_path << Dir[File.dirname(__FILE__) + '/locale/*.{rb,yml}'] - assert_nil backend_get_translations - assert_nothing_raised { @backend.send :init_translations } - assert_not_nil backend_get_translations - end -end - -class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - I18n.load_path = [File.dirname(__FILE__) + '/locale/en.yml'] - assert_nil backend_get_translations - @backend.send :init_translations - end - - def teardown - I18n.load_path = [] - end - - def test_setup - assert_not_nil backend_get_translations - end - - def test_reload_translations_unloads_translations - @backend.reload! - assert_nil backend_get_translations - end - - def test_reload_translations_uninitializes_translations - @backend.reload! - assert_equal @backend.initialized?, false - end -end diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index c4aaba7ab3..4f6ff7d3b5 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -43,10 +43,7 @@ class NilClass private def method_missing(method, *args, &block) - # Ruby 1.9.2: disallow explicit coercion via method_missing. - if method == :to_ary || method == :to_str - super - elsif klass = METHOD_CLASS_MAP[method] + if klass = METHOD_CLASS_MAP[method] raise_nil_warning_for klass, method, caller else super diff --git a/activesupport/test/autoload.rb b/activesupport/test/autoload.rb new file mode 100644 index 0000000000..5d8026a9ca --- /dev/null +++ b/activesupport/test/autoload.rb @@ -0,0 +1,80 @@ +require 'abstract_unit' + +class TestAutoloadModule < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + module ::Fixtures + extend ActiveSupport::Autoload + + module Autoload + extend ActiveSupport::Autoload + end + end + + test "the autoload module works like normal autoload" do + module ::Fixtures::Autoload + autoload :SomeClass, "fixtures/autoload/some_class" + end + + assert_nothing_raised { ::Fixtures::Autoload::SomeClass } + end + + test "when specifying an :eager constant it still works like normal autoload by default" do + module ::Fixtures::Autoload + autoload :SomeClass, "fixtures/autoload/some_class" + end + + assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert_nothing_raised { ::Fixtures::Autoload::SomeClass } + end + + test ":eager constants can be triggered via ActiveSupport::Autoload.eager_autoload!" do + module ::Fixtures::Autoload + autoload :SomeClass, "fixtures/autoload/some_class" + end + ActiveSupport::Autoload.eager_autoload! + assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert_nothing_raised { ::Fixtures::Autoload::SomeClass } + end + + test "the location of autoloaded constants defaults to :name.underscore" do + module ::Fixtures::Autoload + autoload :SomeClass + end + + assert !$LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert_nothing_raised { ::Fixtures::Autoload::SomeClass } + end + + test "the location of :eager autoloaded constants defaults to :name.underscore" do + module ::Fixtures::Autoload + autoload :SomeClass + end + + ActiveSupport::Autoload.eager_autoload! + assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb") + assert_nothing_raised { ::Fixtures::Autoload::SomeClass } + end + + test "a directory for a block of autoloads can be specified" do + module ::Fixtures + autoload_under "autoload" do + autoload :AnotherClass + end + end + + assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") + assert_nothing_raised { ::Fixtures::AnotherClass } + end + + test "a path for a block of autoloads can be specified" do + module ::Fixtures + autoload_at "fixtures/autoload/another_class" do + autoload :AnotherClass + end + end + + assert !$LOADED_FEATURES.include?("fixtures/autoload/another_class.rb") + assert_nothing_raised { ::Fixtures::AnotherClass } + end +end
\ No newline at end of file diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 4341ead488..278c05797b 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -350,6 +350,10 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f end + def test_to_i + assert_equal 946684800, DateTime.civil(2000).to_i + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 66f5f9fbde..4650b796b6 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -89,4 +89,9 @@ class EnumerableTests < Test::Unit::TestCase assert ![ 1, 2 ].many? {|x| x > 1 } assert [ 1, 2, 2 ].many? {|x| x > 1 } end + + def test_exclude? + assert [ 1 ].exclude?(2) + assert ![ 1 ].exclude?(1) + end end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 0fb15be654..4d655913cc 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -17,22 +17,22 @@ class ToQueryTest < Test::Unit::TestCase end def test_nested_conversion - assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', + assert_query_equal 'person[login]=seckar&person[name]=Nicholas', :person => {:name => 'Nicholas', :login => 'seckar'} end def test_multiple_nested - assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', + assert_query_equal 'account[person][id]=20&person[id]=10', :person => {:id => 10}, :account => {:person => {:id => 20}} end def test_array_values - assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', + assert_query_equal 'person[id][]=10&person[id][]=20', :person => {:id => [10, 20]} end def test_array_values_are_not_sorted - assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', + assert_query_equal 'person[id][]=20&person[id][]=10', :person => {:id => [20, 10]} end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index bb60968a4f..3a12100e86 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -284,6 +284,12 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 946684800, result assert result.is_a?(Integer) end + + def test_to_i_with_wrapped_datetime + datetime = DateTime.civil(2000, 1, 1, 0) + twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone) + assert_equal 946684800, twz.to_i + end def test_to_time assert_equal @twz, @twz.to_time diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index a3ae39d071..cf27357b32 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -62,7 +62,7 @@ class DeprecationTest < ActiveSupport::TestCase end def test_deprecate_class_method - assert_deprecated(/none is deprecated.*test_deprecate_class_method at/) do + assert_deprecated(/none is deprecated.*test_deprecate_class_method/) do assert_equal 1, @dtc.none end diff --git a/activesupport/test/fixtures/autoload/another_class.rb b/activesupport/test/fixtures/autoload/another_class.rb new file mode 100644 index 0000000000..a240b3de41 --- /dev/null +++ b/activesupport/test/fixtures/autoload/another_class.rb @@ -0,0 +1,2 @@ +class Fixtures::AnotherClass +end
\ No newline at end of file diff --git a/activesupport/test/fixtures/autoload/some_class.rb b/activesupport/test/fixtures/autoload/some_class.rb new file mode 100644 index 0000000000..13b3c73ef5 --- /dev/null +++ b/activesupport/test/fixtures/autoload/some_class.rb @@ -0,0 +1,2 @@ +class Fixtures::Autoload::SomeClass +end
\ No newline at end of file diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 5d81d09f03..cf9a635b5f 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -23,7 +23,9 @@ class TestJSONEncoding < Test::Unit::TestCase StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")], [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], - [ 'http://test.host/posts/1', %("http://test.host/posts/1")]] + [ 'http://test.host/posts/1', %("http://test.host/posts/1")], + [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F") ]] ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 35d44367cf..4f880d0db7 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -1,221 +1,170 @@ require 'abstract_unit' -# Allow LittleFanout to be cleaned. -class ActiveSupport::Notifications::LittleFanout - def clear - @listeners.clear - end -end - -class NotificationsEventTest < Test::Unit::TestCase - def test_events_are_initialized_with_details - event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar) - assert_equal :foo, event.name - assert_equal Hash[:payload => :bar], event.payload - end - - def test_events_consumes_information_given_as_payload - time = Time.now - event = event(:foo, time, time + 0.01, 1, random_id, {}) - - assert_equal Hash.new, event.payload - assert_equal time, event.time - assert_equal 1, event.result - assert_equal 10.0, event.duration - end - - def test_event_is_parent_based_on_time_frame - time = Time.utc(2009, 01, 01, 0, 0, 1) - - parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {}) - child = event(:foo, time, time + 10, nil, random_id, {}) - not_child = event(:foo, time, time + 100, nil, random_id, {}) - - assert parent.parent_of?(child) - assert !child.parent_of?(parent) - assert !parent.parent_of?(not_child) - assert !not_child.parent_of?(parent) - end +module Notifications + class TestCase < ActiveSupport::TestCase + def setup + Thread.abort_on_exception = true + + @notifier = ActiveSupport::Notifications::Notifier.new + @events = [] + @notifier.subscribe { |*args| @events << event(*args) } + end -protected + def teardown + Thread.abort_on_exception = false + end - def random_id - @random_id ||= ActiveSupport::SecureRandom.hex(10) - end + private + def event(*args) + ActiveSupport::Notifications::Event.new(*args) + end - def event(*args) - ActiveSupport::Notifications::Event.new(*args) + def drain + @notifier.wait + end end -end -class NotificationsMainTest < Test::Unit::TestCase - def setup - @events = [] - Thread.abort_on_exception = true - ActiveSupport::Notifications.subscribe do |*args| - @events << ActiveSupport::Notifications::Event.new(*args) + class PubSubTest < TestCase + def test_events_are_published_to_a_listener + @notifier.publish :foo + @notifier.wait + assert_equal [[:foo]], @events end - end - def teardown - Thread.abort_on_exception = false - ActiveSupport::Notifications.queue.clear - end - - def test_notifications_returns_action_result - result = ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do - 1 + 1 - end + def test_subscriber_with_pattern + events = [] + @notifier.subscribe('1') { |*args| events << args } - assert_equal 2, result - end + @notifier.publish '1' + @notifier.publish '1.a' + @notifier.publish 'a.1' + @notifier.wait - def test_events_are_published_to_a_listener - ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do - 1 + 1 + assert_equal [['1'], ['1.a']], events end - drain - - assert_equal 1, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload - end + def test_subscriber_with_pattern_as_regexp + events = [] + @notifier.subscribe(/\d/) { |*args| events << args } - def test_nested_events_can_be_instrumented - ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do - ActiveSupport::Notifications.instrument(:wot, :payload => "child") do - 1 + 1 - end + @notifier.publish '1' + @notifier.publish 'a.1' + @notifier.publish '1.a' + @notifier.wait - drain - - assert_equal 1, @events.size - assert_equal :wot, @events.first.name - assert_equal Hash[:payload => "child"], @events.first.payload + assert_equal [['1'], ['a.1'], ['1.a']], events end - drain + def test_multiple_subscribers + @another = [] + @notifier.subscribe { |*args| @another << args } + @notifier.publish :foo + @notifier.wait - assert_equal 2, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload - end - - def test_event_is_pushed_even_if_block_fails - ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do - raise "OMG" - end rescue RuntimeError - - drain + assert_equal [[:foo]], @events + assert_equal [[:foo]], @another + end - assert_equal 1, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload + private + def event(*args) + args + end end - def test_event_is_pushed_even_without_block - ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") - drain + class SyncPubSubTest < PubSubTest + def setup + Thread.abort_on_exception = true - assert_equal 1, @events.size - assert_equal :awesome, @events.last.name - assert_equal Hash[:payload => "notifications"], @events.last.payload + @notifier = ActiveSupport::Notifications::Notifier.new(ActiveSupport::Notifications::Fanout.new(true)) + @events = [] + @notifier.subscribe { |*args| @events << event(*args) } + end end - def test_subscribed_in_a_transaction - @another = [] - - ActiveSupport::Notifications.subscribe("cache") do |*args| - @another << ActiveSupport::Notifications::Event.new(*args) + class InstrumentationTest < TestCase + def test_instrument_returns_block_result + assert_equal 2, @notifier.instrument(:awesome) { 1 + 1 } end - ActiveSupport::Notifications.instrument(:cache){ 1 } - ActiveSupport::Notifications.transaction do - ActiveSupport::Notifications.instrument(:cache){ 1 } - end - ActiveSupport::Notifications.instrument(:cache){ 1 } + def test_nested_events_can_be_instrumented + @notifier.instrument(:awesome, :payload => "notifications") do + @notifier.instrument(:wot, :payload => "child") do + 1 + 1 + end - drain + drain - assert_equal 3, @another.size - before, during, after = @another.map {|e| e.transaction_id } - assert_equal before, after - assert_not_equal before, during - end + assert_equal 1, @events.size + assert_equal :wot, @events.first.name + assert_equal Hash[:payload => "child"], @events.first.payload + end - def test_subscriber_with_pattern - @another = [] + drain - ActiveSupport::Notifications.subscribe("cache") do |*args| - @another << ActiveSupport::Notifications::Event.new(*args) + assert_equal 2, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload end - ActiveSupport::Notifications.instrument(:cache){ 1 } - - drain + def test_instrument_publishes_when_exception_is_raised + begin + @notifier.instrument(:awesome, :payload => "notifications") do + raise "OMG" + end + flunk + rescue + end - assert_equal 1, @another.size - assert_equal :cache, @another.first.name - assert_equal 1, @another.first.result - end + drain - def test_subscriber_with_pattern_as_regexp - @another = [] - ActiveSupport::Notifications.subscribe(/cache/) do |*args| - @another << ActiveSupport::Notifications::Event.new(*args) + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload end - ActiveSupport::Notifications.instrument(:something){ 0 } - ActiveSupport::Notifications.instrument(:cache){ 1 } - - drain + def test_event_is_pushed_even_without_block + @notifier.instrument(:awesome, :payload => "notifications") + drain - assert_equal 1, @another.size - assert_equal :cache, @another.first.name - assert_equal 1, @another.first.result + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end end - def test_subscriber_allows_sync_listeners - @another = [] - ActiveSupport::Notifications.subscribe(/cache/, :with => ActiveSupport::Notifications::SyncListener) do |*args| - @another << ActiveSupport::Notifications::Event.new(*args) + class EventTest < TestCase + def test_events_are_initialized_with_details + event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar) + assert_equal :foo, event.name + assert_equal Hash[:payload => :bar], event.payload end - Thread.expects(:new).never - ActiveSupport::Notifications.instrument(:something){ 0 } - ActiveSupport::Notifications.instrument(:cache){ 1 } + def test_events_consumes_information_given_as_payload + time = Time.now + event = event(:foo, time, time + 0.01, 1, random_id, {}) - assert_equal 1, @another.size - assert_equal :cache, @another.first.name - assert_equal 1, @another.first.result - end - - def test_with_several_consumers_and_several_events - @another = [] - ActiveSupport::Notifications.subscribe do |*args| - @another << ActiveSupport::Notifications::Event.new(*args) + assert_equal Hash.new, event.payload + assert_equal time, event.time + assert_equal 1, event.result + assert_equal 10.0, event.duration end - 1.upto(100) do |i| - ActiveSupport::Notifications.instrument(:value){ i } - end + def test_event_is_parent_based_on_time_frame + time = Time.utc(2009, 01, 01, 0, 0, 1) - drain + parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {}) + child = event(:foo, time, time + 10, nil, random_id, {}) + not_child = event(:foo, time, time + 100, nil, random_id, {}) - assert_equal 100, @events.size - assert_equal :value, @events.first.name - assert_equal 1, @events.first.result - assert_equal 100, @events.last.result + assert parent.parent_of?(child) + assert !child.parent_of?(parent) + assert !parent.parent_of?(not_child) + assert !not_child.parent_of?(parent) + end - assert_equal 100, @another.size - assert_equal :value, @another.first.name - assert_equal 1, @another.first.result - assert_equal 100, @another.last.result + protected + def random_id + @random_id ||= ActiveSupport::SecureRandom.hex(10) + end end - - private - def drain - sleep(0.05) until ActiveSupport::Notifications.queue.drained? - end end diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index 009d97940f..1e4f8d854a 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -13,38 +13,39 @@ class WhinyNilTest < Test::Unit::TestCase def test_unchanged nil.method_thats_not_in_whiners rescue NoMethodError => nme - assert(nme.message =~ /nil:NilClass/) + assert_match(/nil:NilClass/, nme.message) end def test_active_record nil.save! rescue NoMethodError => nme - assert(!(nme.message =~ /nil:NilClass/)) + assert_no_match(/nil:NilClass/, nme.message) assert_match(/nil\.save!/, nme.message) end def test_array nil.each rescue NoMethodError => nme - assert(!(nme.message =~ /nil:NilClass/)) + assert_no_match(/nil:NilClass/, nme.message) assert_match(/nil\.each/, nme.message) end def test_id nil.id rescue RuntimeError => nme - assert(!(nme.message =~ /nil:NilClass/)) + assert_no_match(/nil:NilClass/, nme.message) end def test_no_to_ary_coercion nil.to_ary rescue NoMethodError => nme - assert(nme.message =~ /nil:NilClass/) + assert_no_match(/nil:NilClass/, nme.message) + assert_match(/nil\.to_ary/, nme.message) end def test_no_to_str_coercion nil.to_str rescue NoMethodError => nme - assert(nme.message =~ /nil:NilClass/) + assert_match(/nil:NilClass/, nme.message) end end diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml index 776ae8d98d..3dc2000bf9 100644 --- a/ci/geminstaller.yml +++ b/ci/geminstaller.yml @@ -5,4 +5,4 @@ gems: - name: rubygems-update version: >= 1.3.5 - name: bundler - version: >= 0.6.0 + version: >= 0.7.1 diff --git a/rack b/rack new file mode 160000 +Subproject adf996587aecdd604eff441b8b69e4c47a8c261 diff --git a/rack-mount b/rack-mount deleted file mode 160000 -Subproject 3784e633b42f43a4131e02519be60080d179da2 diff --git a/railties/rails.gemspec b/rails.gemspec index dc66e1efea..878a342903 100644 --- a/railties/rails.gemspec +++ b/rails.gemspec @@ -8,22 +8,16 @@ Gem::Specification.new do |s| on top of either MySQL, PostgreSQL, SQLite, DB2, SQL Server, or Oracle with eRuby- or Builder-based templates. EOF - s.add_dependency('rake', '>= 0.8.3') s.add_dependency('activesupport', '= 3.0.pre') - s.add_dependency('activerecord', '= 3.0.pre') s.add_dependency('actionpack', '= 3.0.pre') - s.add_dependency('actionmailer', '= 3.0.pre') + s.add_dependency('activerecord', '= 3.0.pre') s.add_dependency('activeresource', '= 3.0.pre') + s.add_dependency('actionmailer', '= 3.0.pre') + s.add_dependency('railties', '= 3.0.pre') s.rdoc_options << '--exclude' << '.' s.has_rdoc = false - s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'builtin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] - s.require_path = 'lib' - s.bindir = "bin" - s.executables = ["rails"] - s.default_executable = "rails" - s.author = "David Heinemeier Hansson" s.email = "david@loudthinking.com" s.homepage = "http://www.rubyonrails.org" diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 66e0d5e9c5..9ef2922133 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,23 @@ *Edge* +* Added cookies.permanent, cookies.signed, and cookies.permanent.signed accessor for common cookie actions [DHH]. Examples: + + cookies.permanent[:prefers_open_id] = true + # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + + cookies.signed[:discount] = 45 + # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + + cookies.signed[:discount] + # => 45 (if the cookie was changed, you'll get a InvalidSignature exception) + + cookies.permanent.signed[:remember_me] = current_user.id + # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + + ...to use the signed cookies, you need to set a secret to ActionController::Base.cookie_verifier_secret (automatically done in config/initializers/cookie_verification_secret.rb for new Rails applications). + +* Added config/initializers/cookie_verification_secret.rb with an auto-generated secret for using ActionController::Base#cookies.signed [DHH] + * Fixed that the debugger wouldn't go into IRB mode because of left-over ARGVs [DHH] * I18n support for plugins. #2325 [Antonio Tapiador, Sven Fuchs] diff --git a/railties/Rakefile b/railties/Rakefile index e6f698fc74..07c2ff84a0 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -1,3 +1,8 @@ +begin + require File.expand_path('../../vendor/gems/environment', __FILE__) +rescue LoadError +end + require 'rake' require 'rake/testtask' require 'rake/rdoctask' @@ -10,7 +15,7 @@ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" require 'rails/version' PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = ENV['PKG_NAME'] || 'rails' +PKG_NAME = ENV['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}" @@ -141,7 +146,7 @@ Rake::RDocTask.new { |rdoc| # Generate GEM ---------------------------------------------------------------------------- -spec = eval(File.read('rails.gemspec')) +spec = eval(File.read('railties.gemspec')) Rake::GemPackageTask.new(spec) do |pkg| pkg.gem_spec = spec diff --git a/railties/bin/rails b/railties/bin/rails index 808df97429..0f51d5739f 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -4,9 +4,10 @@ rescue LoadError # If people are not using gems, the load path must still # be correct. # TODO: Remove the begin / rescue block somehow - $:.unshift File.dirname(__FILE__) + '/../lib' - $:.unshift File.dirname(__FILE__) + '/../../activesupport/lib' - retry + $:.unshift File.expand_path('../../lib', __FILE__) + $:.unshift File.expand_path('../../../activesupport/lib', __FILE__) + $:.unshift File.expand_path('../../../actionpack/lib', __FILE__) + require 'rails/ruby_version_check' end Signal.trap("INT") { puts; exit } diff --git a/railties/builtin/routes.rb b/railties/builtin/routes.rb new file mode 100644 index 0000000000..ef9d9e756d --- /dev/null +++ b/railties/builtin/routes.rb @@ -0,0 +1,3 @@ +ActionController::Routing::Routes.draw do |map| + match '/rails/info/properties' => "rails/info#properties" +end
\ No newline at end of file diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index c23b67e321..9fb3cd9f94 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1,13 +1,9 @@ -require "pathname" +require "rails/core" -require 'rails/initializable' -require 'rails/application' -require 'rails/railties_path' -require 'rails/version' -require 'rails/rack' -require 'rails/paths' -require 'rails/core' -require 'rails/configuration' -require 'rails/deprecation' -require 'rails/initializer' -require 'rails/plugin'
\ No newline at end of file +%w(active_model active_record action_controller action_view action_mailer active_resource).each do |framework| + begin + require framework + require "#{framework}/rails" + rescue LoadError + end +end
\ No newline at end of file diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 7c2d8eab67..cd579a1c0d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,58 +1,83 @@ +require "fileutils" +require 'active_support/core_ext/module/delegation' + module Rails class Application include Initializable class << self - def inherited(klass) - Rails.application ||= klass unless klass.name =~ /Rails/ - super - end + attr_writer :config + alias configure class_eval + delegate :initialize!, :load_tasks, :to => :instance - # Stub out App initialize - def initialize! - new - end - - def new - @instance ||= super + private :new + def instance + @instance ||= new end def config - @config ||= Configuration.new + @config ||= Configuration.new(Plugin::Configuration.default) end - # TODO: change the plugin loader to use config - alias configuration config - - def config=(config) - @config = config + def routes + ActionController::Routing::Routes end + end - def root - config.root - end + delegate :config, :routes, :to => :'self.class' + delegate :root, :middleware, :to => :config + attr_reader :route_configuration_files - def call(env) - new.call(env) - end + def initialize + require_environment + Rails.application ||= self + @route_configuration_files = [] end - def initialize + def initialize! run_initializers(self) + self end - def config - self.class.config + def require_environment + require config.environment_path + rescue LoadError end - alias configuration config + def routes_changed_at + routes_changed_at = nil - def middleware - config.middleware + route_configuration_files.each do |config| + config_changed_at = File.stat(config).mtime + + if routes_changed_at.nil? || config_changed_at > routes_changed_at + routes_changed_at = config_changed_at + end + end + + routes_changed_at end - def routes - ActionController::Routing::Routes + def reload_routes! + routes.disable_clear_and_finalize = true + + routes.clear! + route_configuration_files.each { |config| load(config) } + routes.finalize! + + nil + ensure + routes.disable_clear_and_finalize = false + end + + def load_tasks + require "rails/tasks" + Dir["#{root}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext } + Dir["#{root}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } + task :environment do + $rails_rake_task = true + initialize! + end end def initializers @@ -61,8 +86,11 @@ module Rails initializers end + # TODO: Fix this method def plugins @plugins ||= begin + plugin_names = config.plugins || [:all] + Plugin.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) } + Plugin::Vendored.all(config.plugins || [:all], config.paths.vendor.plugins) end end @@ -72,8 +100,8 @@ module Rails @app.call(env) end - initializer :initialize_rails do - Rails.run_initializers + initializer :load_all_active_support do + require "active_support/all" unless config.active_support.bare end # Set the <tt>$LOAD_PATH</tt> based on the value of @@ -83,24 +111,6 @@ module Rails $LOAD_PATH.uniq! end - # Requires all frameworks specified by the Configuration#frameworks - # list. By default, all frameworks (Active Record, Active Support, - # Action Pack, Action Mailer, and Active Resource) are loaded. - initializer :require_frameworks do - begin - require 'active_support' - require 'active_support/core_ext/kernel/reporting' - require 'active_support/core_ext/logger' - - # TODO: This is here to make Sam Ruby's tests pass. Needs discussion. - require 'active_support/core_ext/numeric/bytes' - config.frameworks.each { |framework| require(framework.to_s) } - rescue LoadError => e - # Re-raise as RuntimeError because Mongrel would swallow LoadError. - raise e.to_s - end - end - # Set the paths from which Rails will automatically load source files, and # the load_once paths. initializer :set_autoload_paths do @@ -123,25 +133,7 @@ module Rails # Create tmp directories initializer :ensure_tmp_directories_exist do %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(config.root, 'tmp', dir_to_make)) - end - end - - # Loads the environment specified by Configuration#environment_path, which - # is typically one of development, test, or production. - initializer :load_environment do - silence_warnings do - next if @environment_loaded - next unless File.file?(config.environment_path) - - @environment_loaded = true - constants = self.class.constants - - eval(IO.read(config.environment_path), binding, config.environment_path) - - (self.class.constants - constants).each do |const| - Object.const_set(const, self.class.const_get(const)) - end + FileUtils.mkdir_p(File.join(root, 'tmp', dir_to_make)) end end @@ -149,44 +141,7 @@ module Rails # Used by Passenger to ensure everything's loaded before forking and # to avoid autoload race conditions in JRuby. initializer :preload_frameworks do - if config.preload_frameworks - config.frameworks.each do |framework| - # String#classify and #constantize aren't available yet. - toplevel = Object.const_get(framework.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }) - toplevel.load_all! if toplevel.respond_to?(:load_all!) - end - end - end - - # This initialization routine does nothing unless <tt>:active_record</tt> - # is one of the frameworks to load (Configuration#frameworks). If it is, - # this sets the database configuration from Configuration#database_configuration - # and then establishes the connection. - initializer :initialize_database do - if config.frameworks.include?(:active_record) - ActiveRecord::Base.configurations = config.database_configuration - ActiveRecord::Base.establish_connection - end - end - - # Include middleware to serve up static assets - initializer :initialize_static_server do - if config.frameworks.include?(:action_controller) && config.serve_static_assets - config.middleware.use(ActionDispatch::Static, Rails.public_path) - end - end - - initializer :initialize_middleware_stack do - if config.frameworks.include?(:action_controller) - config.middleware.use(::Rack::Lock) unless ActionController::Base.allow_concurrency - config.middleware.use(ActionDispatch::ShowExceptions, ActionController::Base.consider_all_requests_local) - config.middleware.use(ActionDispatch::Callbacks, ActionController::Dispatcher.prepare_each_request) - config.middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) - config.middleware.use(ActionDispatch::ParamsParser) - config.middleware.use(::Rack::MethodOverride) - config.middleware.use(::Rack::Head) - config.middleware.use(ActionDispatch::StringCoercion) - end + ActiveSupport::Autoload.eager_load! if config.preload_frameworks end initializer :initialize_cache do @@ -200,12 +155,6 @@ module Rails end end - initializer :initialize_framework_caches do - if config.frameworks.include?(:action_controller) - ActionController::Base.cache_store ||= RAILS_CACHE - end - end - initializer :initialize_logger do # if the environment has explicitly defined a logger, use it next if Rails.logger @@ -236,10 +185,6 @@ module Rails # logger is already set, it is not changed, otherwise it is set to use # RAILS_DEFAULT_LOGGER. initializer :initialize_framework_logging do - for framework in ([ :active_record, :action_controller, :action_mailer ] & config.frameworks) - framework.to_s.camelize.constantize.const_get("Base").logger ||= Rails.logger - end - ActiveSupport::Dependencies.logger ||= Rails.logger Rails.cache.logger ||= Rails.logger end @@ -257,7 +202,7 @@ module Rails require('active_support/whiny_nil') if config.whiny_nils end - # Sets the default value for Time.zone, and turns on ActiveRecord::Base#time_zone_aware_attributes. + # Sets the default value for Time.zone # If assigned value cannot be matched to a TimeZone, an exception will be raised. initializer :initialize_time_zone do if config.time_zone @@ -271,11 +216,6 @@ module Rails end Time.zone_default = zone_default - - if config.frameworks.include?(:active_record) - ActiveRecord::Base.time_zone_aware_attributes = true - ActiveRecord::Base.default_timezone = :utc - end end end @@ -291,110 +231,28 @@ module Rails end end - # Initializes framework-specific settings for each of the loaded frameworks - # (Configuration#frameworks). The available settings map to the accessors - # on each of the corresponding Base classes. - initializer :initialize_framework_settings do - config.frameworks.each do |framework| - base_class = framework.to_s.camelize.constantize.const_get("Base") - - config.send(framework).each do |setting, value| - base_class.send("#{setting}=", value) - end - end - config.active_support.each do |setting, value| - ActiveSupport.send("#{setting}=", value) - end - end - - # Sets +ActionController::Base#view_paths+ and +ActionMailer::Base#template_root+ - # (but only for those frameworks that are to be loaded). If the framework's - # paths have already been set, it is not changed, otherwise it is - # set to use Configuration#view_path. - initializer :initialize_framework_views do - if config.frameworks.include?(:action_view) - view_path = ActionView::PathSet.type_cast(config.view_path, config.cache_classes) - ActionMailer::Base.template_root = view_path if config.frameworks.include?(:action_mailer) && ActionMailer::Base.view_paths.blank? - ActionController::Base.view_paths = view_path if config.frameworks.include?(:action_controller) && ActionController::Base.view_paths.blank? - end - end - - initializer :initialize_metal do - # TODO: Make Rails and metal work without ActionController - if config.frameworks.include?(:action_controller) - Rails::Rack::Metal.requested_metals = config.metals - - config.middleware.insert_before( - :"ActionDispatch::ParamsParser", - Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?) - end - end - # # bail out if gems are missing - note that check_gem_dependencies will have # # already called abort() unless $gems_rake_task is set # return unless gems_dependencies_loaded initializer :load_application_initializers do - Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer| + Dir["#{root}/config/initializers/**/*.rb"].sort.each do |initializer| load(initializer) end end # Fires the user-supplied after_initialize block (Configuration#after_initialize) initializer :after_initialize do - configuration.after_initialize_blocks.each do |block| + config.after_initialize_blocks.each do |block| block.call end end - # # Setup database middleware after initializers have run - initializer :initialize_database_middleware do - if configuration.frameworks.include?(:active_record) - if configuration.frameworks.include?(:action_controller) && ActionController::Base.session_store && - ActionController::Base.session_store.name == 'ActiveRecord::SessionStore' - configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::ConnectionAdapters::ConnectionManagement - configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::QueryCache - else - configuration.middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement - configuration.middleware.use ActiveRecord::QueryCache - end - end - end - - # TODO: Make a DSL way to limit an initializer to a particular framework - - # # Prepare dispatcher callbacks and run 'prepare' callbacks - initializer :prepare_dispatcher do - next unless configuration.frameworks.include?(:action_controller) - require 'rails/dispatcher' unless defined?(::Dispatcher) - Dispatcher.define_dispatcher_callbacks(configuration.cache_classes) - end - - # Routing must be initialized after plugins to allow the former to extend the routes - # --- - # If Action Controller is not one of the loaded frameworks (Configuration#frameworks) - # this does nothing. Otherwise, it loads the routing definitions and sets up - # loading module used to lazily load controllers (Configuration#controller_paths). - initializer :initialize_routing do - next unless configuration.frameworks.include?(:action_controller) - - ActionController::Routing.controller_paths += configuration.controller_paths - ActionController::Routing::Routes.add_configuration_file(configuration.routes_configuration_file) - ActionController::Routing::Routes.reload! - end - # - # # Observers are loaded after plugins in case Observers or observed models are modified by plugins. - initializer :load_observers do - if configuration.frameworks.include?(:active_record) - ActiveRecord::Base.instantiate_observers - end - end - # Eager load application classes initializer :load_application_classes do next if $rails_rake_task - if configuration.cache_classes - configuration.eager_load_paths.each do |load_path| + if config.cache_classes + config.eager_load_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| require_dependency file.sub(matcher, '\1') @@ -405,7 +263,7 @@ module Rails # Disable dependency loading during request cycle initializer :disable_dependency_loading do - if configuration.cache_classes && !configuration.dependency_loading + if config.cache_classes && !config.dependency_loading ActiveSupport::Dependencies.unhook! end end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index b977b7162f..37eb6d40ea 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -1,45 +1,56 @@ -irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' - require 'optparse' +require 'irb' +require "irb/completion" -options = { :sandbox => false, :irb => irb } -OptionParser.new do |opt| - opt.banner = "Usage: console [environment] [options]" - opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } - opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |v| options[:irb] = v } - opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v } - opt.parse!(ARGV) -end +module Rails + class Console + ENVIRONMENTS = %w(production development test) -libs = " -r irb/completion" -libs << %( -r "#{Rails.root}/config/environment") -libs << " -r rails/console_app" -libs << " -r rails/console_sandbox" if options[:sandbox] -libs << " -r rails/console_with_helpers" - -if options[:debugger] - begin - require 'ruby-debug' - libs << " -r ruby-debug" - puts "=> Debugger enabled" - rescue Exception - puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'" - exit - end -end + def self.start(app) + new(app).start + end -ENV['RAILS_ENV'] = case ARGV.first - when "p"; "production" - when "d"; "development" - when "t"; "test" - else - ARGV.first || ENV['RAILS_ENV'] || 'development' -end + def initialize(app) + @app = app + end + + def start + options = {} -if options[:sandbox] - puts "Loading #{ENV['RAILS_ENV']} environment in sandbox (Rails #{Rails.version})" - puts "Any modifications you make will be rolled back on exit" -else - puts "Loading #{ENV['RAILS_ENV']} environment (Rails #{Rails.version})" + OptionParser.new do |opt| + opt.banner = "Usage: console [environment] [options]" + opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } + opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v } + opt.on('--irb') { |v| abort '--irb option is no longer supported. Invoke `/your/choice/of/ruby script/console` instead' } + opt.parse!(ARGV) + end + + if env = ARGV.first + ENV['RAILS_ENV'] = ENVIRONMENTS.find { |e| e.index(env) } || env + end + + @app.initialize! + require "rails/console_app" + require "rails/console_sandbox" if options[:sandbox] + require "rails/console_with_helpers" + + if options[:debugger] + begin + require 'ruby-debug' + puts "=> Debugger enabled" + rescue Exception + puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'" + exit + end + end + + if options[:sandbox] + puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})" + puts "Any modifications you make will be rolled back on exit" + else + puts "Loading #{Rails.env} environment (Rails #{Rails.version})" + end + IRB.start + end + end end -exec "#{options[:irb]} #{libs} --simple-prompt" diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 4e699acf6b..77c3404343 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -2,86 +2,99 @@ require 'erb' require 'yaml' require 'optparse' -include_password = false -options = {} - -OptionParser.new do |opt| - opt.banner = "Usage: dbconsole [options] [environment]" - opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| - include_password = true - end - - opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'], - "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| - options['mode'] = mode - end - - opt.on("-h", "--header") do |h| - options['header'] = h - end - - opt.parse!(ARGV) - abort opt.to_s unless (0..1).include?(ARGV.size) -end - -env = ARGV.first || ENV['RAILS_ENV'] || 'development' -unless config = YAML::load(ERB.new(IO.read(Rails.root + "/config/database.yml")).result)[env] - abort "No database is configured for the environment '#{env}'" -end - +module Rails + class DBConsole + def self.start(app) + new(app).start + end -def find_cmd(*commands) - dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) - commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/ + def initialize(app) + @app = app + end - full_path_command = nil - found = commands.detect do |cmd| - dir = dirs_on_path.detect do |path| - full_path_command = File.join(path, cmd) - File.executable? full_path_command + def start + include_password = false + options = {} + OptionParser.new do |opt| + opt.banner = "Usage: dbconsole [options] [environment]" + opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| + include_password = true + end + + opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'], + "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| + options['mode'] = mode + end + + opt.on("-h", "--header") do |h| + options['header'] = h + end + + opt.parse!(ARGV) + abort opt.to_s unless (0..1).include?(ARGV.size) + end + + env = ARGV.first || ENV['RAILS_ENV'] || 'development' + unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[env] + abort "No database is configured for the environment '#{env}'" + end + + + def find_cmd(*commands) + dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) + commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/ + + full_path_command = nil + found = commands.detect do |cmd| + dir = dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + File.executable? full_path_command + end + end + found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + + case config["adapter"] + when "mysql" + args = { + 'host' => '--host', + 'port' => '--port', + 'socket' => '--socket', + 'username' => '--user', + 'encoding' => '--default-character-set' + }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact + + if config['password'] && include_password + args << "--password=#{config['password']}" + elsif config['password'] && !config['password'].to_s.empty? + args << "-p" + end + + args << config['database'] + + exec(find_cmd('mysql', 'mysql5'), *args) + + when "postgresql" + ENV['PGUSER'] = config["username"] if config["username"] + ENV['PGHOST'] = config["host"] if config["host"] + ENV['PGPORT'] = config["port"].to_s if config["port"] + ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password + exec(find_cmd('psql'), config["database"]) + + when "sqlite" + exec(find_cmd('sqlite'), config["database"]) + + when "sqlite3" + args = [] + + args << "-#{options['mode']}" if options['mode'] + args << "-header" if options['header'] + args << config['database'] + + exec(find_cmd('sqlite3'), *args) + else + abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!" + end end end - found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") -end - -case config["adapter"] -when "mysql" - args = { - 'host' => '--host', - 'port' => '--port', - 'socket' => '--socket', - 'username' => '--user', - 'encoding' => '--default-character-set' - }.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact - - if config['password'] && include_password - args << "--password=#{config['password']}" - elsif config['password'] && !config['password'].to_s.empty? - args << "-p" - end - - args << config['database'] - - exec(find_cmd('mysql', 'mysql5'), *args) - -when "postgresql" - ENV['PGUSER'] = config["username"] if config["username"] - ENV['PGHOST'] = config["host"] if config["host"] - ENV['PGPORT'] = config["port"].to_s if config["port"] - ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password - exec(find_cmd('psql'), config["database"]) - -when "sqlite" - exec(find_cmd('sqlite'), config["database"]) - -when "sqlite3" - args = [] - - args << "-#{options['mode']}" if options['mode'] - args << "-header" if options['header'] - args << config['database'] - - exec(find_cmd('sqlite3'), *args) -else - abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!" -end +end
\ No newline at end of file diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 2c90851fb2..09d7207d51 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -1,73 +1,76 @@ -require 'action_dispatch' - require 'fileutils' require 'optparse' +require 'action_dispatch' -options = { - :Port => 3000, - :Host => "0.0.0.0", - :environment => (ENV['RAILS_ENV'] || "development").dup, - :config => "#{Rails.root}/config.ru", - :detach => false, - :debugger => false -} - -ARGV.clone.options do |opts| - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", "Default: #{options[:Port]}") { |v| options[:Port] = v } - opts.on("-b", "--binding=ip", String, - "Binds Rails to the specified ip.", "Default: #{options[:Host]}") { |v| options[:Host] = v } - opts.on("-c", "--config=file", String, - "Use custom rackup configuration file") { |v| options[:config] = v } - opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:detach] = true } - opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true } - opts.on("-e", "--environment=name", String, - "Specifies the environment to run this server under (test/development/production).", - "Default: #{options[:environment]}") { |v| options[:environment] = v } - - opts.separator "" +module Rails + class Server < ::Rack::Server + class Options + def parse!(args) + options = {} + args = args.dup + opt_parser = OptionParser.new do |opts| + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", "Default: #{options[:Port]}") { |v| options[:Port] = v } + opts.on("-b", "--binding=ip", String, + "Binds Rails to the specified ip.", "Default: #{options[:Host]}") { |v| options[:Host] = v } + opts.on("-c", "--config=file", String, + "Use custom rackup configuration file") { |v| options[:config] = v } + opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true } + opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: #{options[:environment]}") { |v| options[:environment] = v } - opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + opts.separator "" - opts.parse! -end + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + end -server = Rack::Handler.get(ARGV.first) rescue nil -unless server - begin - server = Rack::Handler::Mongrel - rescue LoadError => e - server = Rack::Handler::WEBrick - end -end + opt_parser.parse! args -puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" -puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}" + options[:server] = args.shift + options + end + end -if options[:detach] - Process.daemon - pid = "#{Rails.root}/tmp/pids/server.pid" - File.open(pid, 'w'){ |f| f.write(Process.pid) } - at_exit { File.delete(pid) if File.exist?(pid) } -end + def opt_parser + Options.new + end -ENV["RAILS_ENV"] = options[:environment] -RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV) + def start + puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" + puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}" + puts "=> Call with -d to detach" unless options[:daemonize] + trap(:INT) { exit } + puts "=> Ctrl-C to shutdown server" unless options[:daemonize] -app = Rack::Builder.new { - use Rails::Rack::LogTailer unless options[:detach] - use Rails::Rack::Debugger if options[:debugger] - run ActionDispatch::Utils.parse_config(options[:config]) -}.to_app + ENV["RAILS_ENV"] = options[:environment] + RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV) -puts "=> Call with -d to detach" + super + ensure + puts 'Exiting' unless options[:daemonize] + end -trap(:INT) { exit } + def middleware + middlewares = [] + middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize] + middlewares << [Rails::Rack::Debugger] if options[:debugger] + Hash.new(middlewares) + end -puts "=> Ctrl-C to shutdown server" + def log_path + "log/#{options[:environment]}.log" + end -begin - server.run(app, options.merge(:AccessLog => [])) -ensure - puts 'Exiting' + def default_options + super.merge({ + :Port => 3000, + :environment => (ENV['RAILS_ENV'] || "development").dup, + :daemonize => false, + :debugger => false, + :pid => "tmp/pids/server.pid" + }) + end + end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 102a0836dc..086f67a419 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,27 +1,74 @@ require 'active_support/ordered_options' module Rails - class Configuration - attr_accessor :cache_classes, :load_paths, :load_once_paths, :after_initialize_blocks, - :frameworks, :framework_root_path, :root, :gems, :plugins, - :i18n, :gems, :whiny_nils, :consider_all_requests_local, - :action_controller, :active_record, :action_view, :active_support, - :action_mailer, :active_resource, - :reload_plugins, :log_path, :log_level, :logger, :preload_frameworks, - :database_configuration_file, :cache_store, :time_zone, - :view_path, :metals, :controller_paths, :routes_configuration_file, - :eager_load_paths, :dependency_loading, :paths, :serve_static_assets - - def initialize + # Temporarily separate the plugin configuration class from the main + # configuration class while this bit is being cleaned up. + class Plugin::Configuration + + def self.default + @default ||= new + end + + attr_reader :middleware + + def initialize(base = nil) + if base + @options = base.options.dup + @middleware = base.middleware.dup + else + @options = Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new } + @middleware = ActionDispatch::MiddlewareStack.new + end + end + + def respond_to?(name) + super || name.to_s =~ config_key_regexp + end + + protected + + attr_reader :options + + 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 + ([ :active_support, :action_view, :action_mailer, :active_resource ] + + Plugin.plugin_names).map { |n| n.to_s }.uniq + end + end + + class Configuration < Plugin::Configuration + attr_accessor :after_initialize_blocks, :cache_classes, + :consider_all_requests_local, :dependency_loading, :gems, + :load_once_paths, :logger, :metals, :plugins, + :preload_frameworks, :reload_plugins, :serve_static_assets, + :time_zone, :whiny_nils + + attr_writer :cache_store, :controller_paths, + :database_configuration_file, :eager_load_paths, + :frameworks, :framework_root_path, :i18n, :load_paths, + :log_level, :log_path, :paths, :routes_configuration_file, + :view_path + + def initialize(base = nil) + super @load_once_paths = [] @after_initialize_blocks = [] @dependency_loading = true @serve_static_assets = true - - for framework in frameworks - self.send("#{framework}=", ActiveSupport::OrderedOptions.new) - end - self.active_support = ActiveSupport::OrderedOptions.new end def after_initialize(&blk) @@ -31,7 +78,7 @@ module Rails def root @root ||= begin call_stack = caller.map { |p| p.split(':').first } - root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] } + root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails|rack/lib/rack] } root_path = File.dirname(root_path) while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru") @@ -80,7 +127,10 @@ module Rails self.preload_frameworks = true self.cache_classes = true self.dependency_loading = false - self.action_controller.allow_concurrency = true + + if respond_to?(:action_controller) + action_controller.allow_concurrency = true + end self end @@ -99,11 +149,6 @@ module Rails defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root}/vendor/rails" end - def middleware - require 'action_dispatch' - @middleware ||= ActionDispatch::MiddlewareStack.new - end - # Loads and returns the contents of the #database_configuration_file. The # contents of the file are processed via ERB before being sent through # YAML::load. @@ -116,6 +161,10 @@ module Rails @routes_configuration_file ||= File.join(root, 'config', 'routes.rb') end + def builtin_routes_configuration_file + @builtin_routes_configuration_file ||= File.join(RAILTIES_PATH, 'builtin', 'routes.rb') + end + def controller_paths @controller_paths ||= begin paths = [File.join(root, 'app', 'controllers')] diff --git a/railties/lib/rails/console_app.rb b/railties/lib/rails/console_app.rb index 1ad62e5058..2c4a7a51e8 100644 --- a/railties/lib/rails/console_app.rb +++ b/railties/lib/rails/console_app.rb @@ -27,6 +27,6 @@ end def reload! puts "Reloading..." ActionDispatch::Callbacks.new(lambda {}, true) - ActionController::Routing::Routes.reload + Rails.application.reload_routes! true end diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb index a5e51ad04a..da16c5816c 100644 --- a/railties/lib/rails/core.rb +++ b/railties/lib/rails/core.rb @@ -1,3 +1,37 @@ +require "pathname" + +require 'active_support' +require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/logger' +require 'action_dispatch' + +require 'rails/initializable' +require 'rails/application' +require 'rails/plugin' +require 'rails/railties_path' +require 'rails/version' +require 'rails/rack' +require 'rails/paths' +require 'rails/core' +require 'rails/configuration' +require 'rails/deprecation' +require 'rails/initializer' +require 'rails/ruby_version_check' + +# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the +# multibyte safe operations. Plugin authors supporting other encodings +# should override this behaviour and set the relevant +default_charset+ +# on ActionController::Base. +# +# For Ruby 1.9, UTF-8 is the default internal and external encoding. +if RUBY_VERSION < '1.9' + $KCODE='u' +else + Encoding.default_external = Encoding::UTF_8 +end + +RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV) + module Rails # Needs to be duplicated from Active Support since its needed before Active # Support is available. Here both Options and Hash are namespaced to prevent diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 85c2fd52a4..0e66c9f58f 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -1,5 +1,6 @@ -activesupport_path = "#{File.dirname(__FILE__)}/../../../activesupport/lib" -$LOAD_PATH.unshift(activesupport_path) if File.directory?(activesupport_path) +activesupport_path = File.expand_path('../../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + require 'active_support' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/metaclass' @@ -9,7 +10,7 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/inflections' # TODO: Do not always push on vendored thor -$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.0/lib") +$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.1/lib") require 'rails/generators/base' require 'rails/generators/named_base' diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 8677bf283b..f95b15acce 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -104,7 +104,7 @@ module Rails # file in config/environments. # def environment(data=nil, options={}, &block) - sentinel = "Rails::Initializer.run do |config|" + sentinel = /class [a-z_:]+ < Rails::Application/i data = block.call if !data && block_given? in_root do @@ -269,11 +269,11 @@ module Rails # # === Example # - # route "map.root :controller => :welcome" + # route "root :to => 'welcome'" # def route(routing_code) log :route, routing_code - sentinel = "ActionController::Routing::Routes.draw do |map|" + sentinel = "routes.draw do |map|" in_root do inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false } diff --git a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb index 6460e5b599..51c4ad0e2e 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb @@ -7,7 +7,7 @@ </head> <body> -<p class="notice"><%%= flash[:notice] %></p> +<p class="notice"><%%= notice %></p> <%%= yield %> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 2bcea4bc8f..30272ed9b2 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -48,9 +48,9 @@ module Rails::Generators end def create_root_files - copy_file "Rakefile" copy_file "README" - copy_file "config.ru" + template "Rakefile" + template "config.ru" template "Gemfile" end @@ -62,9 +62,9 @@ module Rails::Generators empty_directory "config" inside "config" do - copy_file "routes.rb" - template "application.rb" - template "environment.rb" + template "routes.rb" + template "application.rb" + template "environment.rb" directory "environments" directory "initializers" @@ -123,10 +123,10 @@ module Rails::Generators end def create_script_files - directory "script" do |file| - prepend_file file, "#{shebang}\n", :verbose => false - chmod file, 0755, :verbose => false + directory "script" do |content| + "#{shebang}\n" + content end + chmod "script", 0755, :verbose => false end def create_test_files @@ -181,6 +181,10 @@ module Rails::Generators @app_name ||= File.basename(destination_root) end + def app_const + @app_const ||= "#{app_name.classify}::Application" + end + def app_secret ActiveSupport::SecureRandom.hex(64) end diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile index 6b6d07e8cc..c19ad0e945 100755 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -7,4 +7,4 @@ require 'rake' require 'rake/testtask' require 'rake/rdoctask' -require 'rails/tasks' +<%= app_const %>.load_tasks diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb index 6635a3f487..9889b52893 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb @@ -2,9 +2,7 @@ # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base - helper :all # include all helpers, all the time - protect_from_forgery # See ActionController::RequestForgeryProtection for details - - # Scrub sensitive parameters from your log - # filter_parameter_logging :password + helper :all + protect_from_forgery + filter_parameter_logging :password end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru index 509a0da5b7..acb8435446 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -2,4 +2,4 @@ require ::File.expand_path('../config/environment', __FILE__) # Dispatch the request -run Rails.application +run <%= app_const %>.instance diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 8008c6ba07..15dc553e53 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -1,41 +1,43 @@ require File.expand_path('../boot', __FILE__) -Rails::Initializer.run do |config| - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. +module <%= app_name.classify %> + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. - # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{root}/extras ) + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{root}/extras ) - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Skip frameworks you're not going to use. To use Rails without a database, - # you must remove the Active Record framework. + # Skip frameworks you're not going to use. To use Rails without a database, + # you must remove the Active Record framework. <% if options[:skip_activerecord] -%> - config.frameworks -= [ :active_record ] + config.frameworks -= [ :active_record ] <% else -%> - # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] - # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer <% end -%> - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. - config.time_zone = 'UTC' - - # 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.default_locale = :de - - # Configure generators values. Many other options are available, be sure to - # check the documentation. - # config.generators do |g| - # g.orm :active_record - # g.template_engine :erb - # g.test_framework :test_unit, :fixture => true - # end + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. + config.time_zone = 'UTC' + + # 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.default_locale = :de + + # Configure generators values. Many other options are available, be sure to + # check the documentation. + # config.generators do |g| + # g.orm :active_record + # g.template_engine :erb + # g.test_framework :test_unit, :fixture => true + # end + end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index 0bb191f205..1684986a59 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application -Rails.initialize! +<%= app_const %>.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb deleted file mode 100644 index 85c9a6080e..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# In the development environment your application's code is reloaded on -# every request. This slows down response time but is perfect for development -# since you don't have to restart the webserver when you make code changes. -config.cache_classes = false - -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true - -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_view.debug_rjs = true -config.action_controller.perform_caching = false - -# Don't care if the mailer can't send -config.action_mailer.raise_delivery_errors = false
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt new file mode 100644 index 0000000000..b10103b436 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -0,0 +1,19 @@ +<%= app_const %>.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.action_controller.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false +end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb deleted file mode 100644 index 377b9207c7..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb +++ /dev/null @@ -1,31 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true - -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true - -# See everything in the log (default is :info) -# config.log_level = :debug - -# Use a different logger for distributed setups -# config.logger = SyslogLogger.new - -# Use a different cache store in production -# config.cache_store = :mem_cache_store - -# Disable Rails's static asset server -# In production, Apache or nginx will already do this -config.serve_static_assets = false - -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" - -# Disable delivery errors, bad email addresses will be ignored -# config.action_mailer.raise_delivery_errors = false - -# Enable threaded mode -# config.threadsafe! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt new file mode 100644 index 0000000000..543a40108c --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -0,0 +1,33 @@ +<%= app_const %>.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.action_controller.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! +end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb deleted file mode 100644 index 496eb9572b..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The test environment is used exclusively to run your application's -# test suite. You never need to work with it otherwise. Remember that -# your test database is "scratch space" for the test suite and is wiped -# and recreated between test runs. Don't rely on the data there! -config.cache_classes = true - -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true - -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_controller.perform_caching = false - -# Disable request forgery protection in test environment -config.action_controller.allow_forgery_protection = false - -# Tell Action Mailer not to deliver emails to the real world. -# The :test delivery method accumulates sent emails in the -# ActionMailer::Base.deliveries array. -config.action_mailer.delivery_method = :test - -# Use SQL instead of Active Record's schema dumper when creating the test database. -# This is necessary if your schema can't be completely dumped by the schema dumper, -# like if you have constraints or database-specific column types -# config.active_record.schema_format = :sql
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt new file mode 100644 index 0000000000..428fa35633 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -0,0 +1,29 @@ +<%= app_const %>.configure do + # Settings specified here will take precedence over those in config/environment.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.action_controller.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql +end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt new file mode 100644 index 0000000000..9f05cd5a31 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# 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 %>'; diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index ea14ce1bfc..d6c0365c04 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,43 +1,58 @@ -ActionController::Routing::Routes.draw do |map| - # The priority is based upon order of creation: first created -> highest priority. +<%= app_const %>.routes.draw do |map| + # The priority is based upon order of creation: + # first created -> highest priority. # Sample of regular route: - # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # match 'products/:id' => 'catalog#view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: - # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase # This route can be invoked with purchase_url(:id => product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): - # map.resources :products + # resources :products # Sample resource route with options: - # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } + # resources :products do + # member do + # get :short + # post :toggle + # end + # + # collection do + # get :sold + # end + # end # Sample resource route with sub-resources: - # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller - + # resources :products do + # resources :comments, :sales + # resource :seller + # end + # Sample resource route with more complex sub-resources - # map.resources :products do |products| - # products.resources :comments - # products.resources :sales, :collection => { :recent => :get } + # resources :products do + # resources :comments + # resources :sales do + # get :recent, :on => :collection + # end # end # Sample resource route within a namespace: - # map.namespace :admin do |admin| - # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) - # admin.resources :products + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products # end - # You can have the root of your site routed with map.root -- just remember to delete public/index.html. - # map.root :controller => "welcome" + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => "welcome#index" # See how all your routes lay out with "rake routes" - # Install the default routes as the lowest priority. - # Note: These default routes make all actions in every controller accessible via GET requests. You should - # consider removing or commenting them out if you're using named routes and resources. - map.connect ':controller/:action/:id' - map.connect ':controller/:action/:id.:format' + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id(.:format)))' end diff --git a/railties/lib/rails/generators/rails/app/templates/script/console b/railties/lib/rails/generators/rails/app/templates/script/console.tt index 20aa799d2f..9ddd4cfe62 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/console +++ b/railties/lib/rails/generators/rails/app/templates/script/console.tt @@ -1,2 +1,3 @@ require File.expand_path('../../config/application', __FILE__) require 'rails/commands/console' +Rails::Console.start(<%= app_const %>.instance) diff --git a/railties/lib/rails/generators/rails/app/templates/script/dbconsole b/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt index e6a1c59394..96e0bc191b 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/dbconsole +++ b/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt @@ -1,2 +1,3 @@ require File.expand_path('../../config/application', __FILE__) require 'rails/commands/dbconsole' +Rails::DBConsole.start(<%= app_const %>.instance) diff --git a/railties/lib/rails/generators/rails/app/templates/script/server b/railties/lib/rails/generators/rails/app/templates/script/server deleted file mode 100755 index a7aaee2953..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/server +++ /dev/null @@ -1,2 +0,0 @@ -require File.expand_path('../../config/application', __FILE__) -require 'rails/commands/server' diff --git a/railties/lib/rails/generators/rails/app/templates/script/server.tt b/railties/lib/rails/generators/rails/app/templates/script/server.tt new file mode 100755 index 0000000000..4fd0cc7832 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/server.tt @@ -0,0 +1,5 @@ +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands/server' + +Dir.chdir(File.expand_path('../..', __FILE__)) +Rails::Server.start diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt index 675f00043f..d8757460e4 100644 --- a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt +++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt @@ -1,5 +1,5 @@ class <%= class_name %>Generator < Rails::Generators::NamedBase def self.source_root - @source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates')) + @source_root ||= File.expand_path('../templates', __FILE__) end end diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index 85e8ff1834..23c2245a41 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -10,7 +10,6 @@ Rake::TestTask.new(:test) do |t| t.libs << 'lib' t.libs << 'test' t.pattern = 'test/**/*_test.rb' - t.verbose = true end desc 'Generate documentation for the <%= file_name %> plugin.' diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index e49f9aea1b..a89ce7faed 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -16,7 +16,7 @@ module Rails class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller" def add_resource_route - route "map.resource#{:s unless options[:singleton]} :#{pluralize?(file_name)}" + route "resource#{:s unless options[:singleton]} :#{pluralize?(file_name)}" end protected diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE index 71edd2f469..530ccdaf0a 100644 --- a/railties/lib/rails/generators/rails/scaffold/USAGE +++ b/railties/lib/rails/generators/rails/scaffold/USAGE @@ -17,7 +17,7 @@ Description: For example, 'scaffold post title:string body:text published:boolean' gives you a model with those three attributes, a controller that handles the create/show/update/destroy, forms to create and edit your posts, and - an index that lists them all, as well as a map.resources :posts + an index that lists them all, as well as a resources :posts declaration in config/routes.rb. If you want to remove all the generated files, run diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 3cc8bbf8e7..874e96a2b4 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -46,8 +46,7 @@ class <%= controller_class_name %>Controller < ApplicationController respond_to do |format| if @<%= orm_instance.save %> - flash[:notice] = '<%= class_name %> was successfully created.' - format.html { redirect_to(@<%= file_name %>) } + format.html { redirect_to(@<%= file_name %>, :notice => '<%= class_name %> was successfully created.') } format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> } else format.html { render :action => "new" } @@ -63,8 +62,7 @@ class <%= controller_class_name %>Controller < ApplicationController respond_to do |format| if @<%= orm_instance.update_attributes("params[:#{file_name}]") %> - flash[:notice] = '<%= class_name %> was successfully updated.' - format.html { redirect_to(@<%= file_name %>) } + format.html { redirect_to(@<%= file_name %>, :notice => '<%= class_name %> was successfully updated.') } format.xml { head :ok } else format.html { render :action => "edit" } diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index c21035113e..a30132bc99 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -11,9 +11,13 @@ two: <%= attribute.name %>: <%= attribute.default %> <% end -%> <% else -%> -# one: -# column: value +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below # -# two: -# column: value -<% end -%> +one: {} +# column: value +# +two: {} +# column: value +<% end -%>
\ No newline at end of file diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index 3866b856b2..8fcb254590 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -5,7 +5,7 @@ module Rails end class Initializer - attr_reader :name, :before, :after, :global, :block + attr_reader :name, :block def initialize(name, context, options, &block) @name, @context, @options, @block = name, context, options, block @@ -62,7 +62,7 @@ module Rails end def run_initializers(*args) - return if @ran + return if instance_variable_defined?(:@ran) initializers.each do |initializer| initializer.run(*args) end @@ -93,6 +93,7 @@ module Rails end def initializer(name, opts = {}, &blk) + raise ArgumentError, "A block must be passed when defining an initializer" unless blk @initializers ||= [] @initializers << Initializer.new(name, nil, opts, &blk) end @@ -106,26 +107,4 @@ module Rails end end end - - include Initializable - - # Check for valid Ruby version (1.8.2 or 1.8.4 or higher). This is done in an - # external file, so we can use it from the `rails` program as well without duplication. - initializer :check_ruby_version, :global => true do - require 'rails/ruby_version_check' - end - - # For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the - # multibyte safe operations. Plugin authors supporting other encodings - # should override this behaviour and set the relevant +default_charset+ - # on ActionController::Base. - # - # For Ruby 1.9, UTF-8 is the default internal and external encoding. - initializer :initialize_encoding, :global => true do - if RUBY_VERSION < '1.9' - $KCODE='u' - else - Encoding.default_external = Encoding::UTF_8 - end - end end
\ No newline at end of file diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb index 44d04688c8..95478428ec 100644 --- a/railties/lib/rails/initializer.rb +++ b/railties/lib/rails/initializer.rb @@ -1,7 +1,5 @@ require "rails" # In case people require this file directly -RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) - module Rails class Initializer class Error < StandardError ; end @@ -14,4 +12,4 @@ module Rails end end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 86bf032641..0699affea7 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -2,6 +2,29 @@ module Rails class Plugin include Initializable + def self.plugin_name(plugin_name = nil) + @plugin_name ||= name.demodulize.underscore + @plugin_name = plugin_name if plugin_name + @plugin_name + end + + def self.inherited(klass) + @plugins ||= [] + @plugins << klass unless klass == Vendored + end + + def self.plugins + @plugins + end + + def self.plugin_names + plugins.map { |p| p.plugin_name } + end + + def self.config + Configuration.default + end + class Vendored < Plugin def self.all(list, paths) plugins = [] @@ -55,8 +78,8 @@ module Rails initializer :add_routing_file, :after => :initialize_routing do |app| routing_file = "#{path}/config/routes.rb" if File.exist?(routing_file) - app.routes.add_configuration_file(routing_file) - app.routes.reload! + app.route_configuration_files << routing_file + app.reload_routes! end end end diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb index a237cee6bc..077311be3c 100644 --- a/railties/lib/rails/rack/log_tailer.rb +++ b/railties/lib/rails/rack/log_tailer.rb @@ -1,12 +1,10 @@ module Rails module Rack class LogTailer - EnvironmentLog = "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log" - def initialize(app, log = nil) @app = app - path = Pathname.new(log || EnvironmentLog).cleanpath + path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath @cursor = ::File.size(path) @last_checked = Time.now.to_f diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 82113a297c..dc886f4a4d 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -16,8 +16,3 @@ $VERBOSE = nil ).each do |task| load "rails/tasks/#{task}.rake" end - -# Load any custom rakefile extensions -# TODO: Don't hardcode these paths. -Dir["#{Rails.root}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext } -Dir["#{Rails.root}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 1611d1d94d..f7b53885c8 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -92,7 +92,7 @@ namespace :rails do namespace :update do def invoke_from_app_generator(method) - require 'generators' + require 'rails/generators' require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, @@ -100,6 +100,12 @@ namespace :rails do generator.invoke(method) end + desc "Update config/boot.rb from your current rails install" + task :configs do + invoke_from_app_generator :create_boot_file + invoke_from_app_generator :create_config_files + end + desc "Update Prototype javascripts from your current rails install" task :javascripts do invoke_from_app_generator :create_prototype_files diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index 7f244ebaed..9433b3556a 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -1,8 +1,4 @@ task :default => :test -task :environment do - $rails_rake_task = true - require(File.join(Rails.root, 'config', 'environment')) -end task :rails_env do unless defined? RAILS_ENV diff --git a/railties/lib/rails/tasks/testing.rake b/railties/lib/rails/tasks/testing.rake index fd5e52a05b..57857fb911 100644 --- a/railties/lib/rails/tasks/testing.rake +++ b/railties/lib/rails/tasks/testing.rake @@ -48,7 +48,7 @@ task :test do task end end.compact - abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any? + abort "Errors running #{errors * ', '}!" if errors.any? end namespace :test do @@ -59,7 +59,6 @@ namespace :test do recent_tests('app/controllers/**/*.rb', 'test/functional', since) t.libs << 'test' - t.verbose = true t.test_files = touched.uniq end Rake::Task['test:recent'].comment = "Test recent changes" @@ -84,35 +83,30 @@ namespace :test do end t.libs << 'test' - t.verbose = true end Rake::Task['test:uncommitted'].comment = "Test changes since last checkin (only Subversion and Git)" Rake::TestTask.new(:units => "db:test:prepare") do |t| t.libs << "test" t.pattern = 'test/unit/**/*_test.rb' - t.verbose = true end Rake::Task['test:units'].comment = "Run the unit tests in test/unit" Rake::TestTask.new(:functionals => "db:test:prepare") do |t| t.libs << "test" t.pattern = 'test/functional/**/*_test.rb' - t.verbose = true end Rake::Task['test:functionals'].comment = "Run the functional tests in test/functional" Rake::TestTask.new(:integration => "db:test:prepare") do |t| t.libs << "test" t.pattern = 'test/integration/**/*_test.rb' - t.verbose = true end Rake::Task['test:integration'].comment = "Run the integration tests in test/integration" Rake::TestTask.new(:benchmark => 'db:test:prepare') do |t| t.libs << 'test' t.pattern = 'test/performance/**/*_test.rb' - t.verbose = true t.options = '-- --benchmark' end Rake::Task['test:benchmark'].comment = 'Benchmark the performance tests' @@ -120,7 +114,6 @@ namespace :test do Rake::TestTask.new(:profile => 'db:test:prepare') do |t| t.libs << 'test' t.pattern = 'test/performance/**/*_test.rb' - t.verbose = true end Rake::Task['test:profile'].comment = 'Profile the performance tests' @@ -132,8 +125,6 @@ namespace :test do else t.pattern = 'vendor/plugins/*/**/test/**/*_test.rb' end - - t.verbose = true end Rake::Task['test:plugins'].comment = "Run the plugin tests in vendor/plugins/*/**/test (or specify with PLUGIN=name)" end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 9f6c42945f..2601765065 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -2,14 +2,18 @@ # so fixtures are loaded to the right database silence_warnings { RAILS_ENV = "test" } -require 'rubygems' -gem "rack", "~> 1.0.0" -gem "rack-test", "~> 0.5.0" +require 'rack' +require 'rack/test' require 'test/unit' require 'active_support/core_ext/kernel/requires' + +# AP is always present +require 'action_controller/test_case' require 'action_view/test_case' + require 'action_mailer/test_case' if defined?(ActionMailer) +require 'active_model/test_case' if defined?(ActiveModel) if defined?(ActiveRecord) require 'active_record/test_case' diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb deleted file mode 100644 index 885230fac4..0000000000 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Thor - VERSION = "0.11.8".freeze -end diff --git a/railties/lib/rails/vendor/thor-0.12.0/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc index adedfeca9d..606a0cdb52 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/CHANGELOG.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc @@ -17,7 +17,7 @@ * thor help now show information about any class/task. All those calls are possible: - + thor help describe thor help describe:amazing @@ -47,7 +47,7 @@ are in the 'standard' group. Running 'thor -T' will only show the standard tasks - adding --all will show all tasks. You can also filter on a specific group using the --group option: thor -T --group advanced - + == 0.9.6, released 2008-09-13 * Generic improvements diff --git a/railties/lib/rails/vendor/thor-0.12.0/LICENSE b/railties/lib/rails/vendor/thor-0.12.1/LICENSE index 98722da459..98722da459 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/LICENSE +++ b/railties/lib/rails/vendor/thor-0.12.1/LICENSE diff --git a/railties/lib/rails/vendor/thor-0.12.0/README.rdoc b/railties/lib/rails/vendor/thor-0.12.1/README.rdoc index f1106f02b6..ee545f3d97 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/README.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.1/README.rdoc @@ -7,7 +7,7 @@ Example: class App < Thor # [1] map "-L" => :list # [2] - + desc "install APP_NAME", "install one of the available apps" # [3] method_options :force => :boolean, :alias => :string # [4] def install(name) @@ -17,7 +17,7 @@ Example: end # other code end - + desc "list [SEARCH]", "list all of the available apps, limited by SEARCH" def list(search="") # list everything @@ -126,13 +126,13 @@ invoked only once. For example: invoke :two invoke :three end - + desc "two", "Prints 2, 3" def two puts 2 invoke :three end - + desc "three", "Prints 3" def three puts 3 @@ -155,15 +155,15 @@ Thor::Group as this: class Counter < Thor::Group desc "Prints 1, 2, 3" - + def one puts 1 end - + def two puts 2 end - + def three puts 3 end @@ -184,15 +184,15 @@ Besides, Thor::Group can parse arguments and options as Thor tasks: # number will be available as attr_accessor argument :number, :type => :numeric, :desc => "The number to start counting" desc "Prints the 'number' given upto 'number+2'" - + def one puts number + 0 end - + def two puts number + 1 end - + def three puts number + 2 end diff --git a/railties/lib/rails/vendor/thor-0.12.0/Thorfile b/railties/lib/rails/vendor/thor-0.12.1/Thorfile index f71a1e57e2..ff1cb4498a 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/Thorfile +++ b/railties/lib/rails/vendor/thor-0.12.1/Thorfile @@ -56,7 +56,7 @@ class Default < Thor s.test_files.exclude 'spec/sandbox/**/*' end - Jeweler::RubyforgeTasks.new + Jeweler::GemcutterTasks.new rescue LoadError puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" end diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb index 68944f140d..68944f140d 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb index d561ccb2aa..4bfb7c2870 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb @@ -114,7 +114,7 @@ class Thor @source_paths ||= self.class.source_paths_for_search end - # Receives a file or directory and search for it in the source paths. + # Receives a file or directory and search for it in the source paths. # def find_in_source_paths(file) relative_root = relative_to_original_destination_root(destination_root, false) @@ -222,7 +222,7 @@ class Thor run "#{command}", config.merge(:with => Thor::Util.ruby_command) end - # Run a thor command. A hash of options can be given and it's converted to + # Run a thor command. A hash of options can be given and it's converted to # switches. # # ==== Parameters diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb index a3d9296823..a3d9296823 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/create_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb index 467e63732a..2e0b459fa3 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb @@ -79,11 +79,9 @@ class Thor next if dirname == given_destination base.empty_directory(dirname, config) when /\.tt$/ - destination = base.template(file_source, file_destination[0..-4], config) - @block.call(destination) if @block + destination = base.template(file_source, file_destination[0..-4], config, &@block) else - destination = base.copy_file(file_source, file_destination, config) - @block.call(destination) if @block + destination = base.copy_file(file_source, file_destination, config, &@block) end end end diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb index 484cb820f8..484cb820f8 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/empty_directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb index d77d90d448..8a45c83f25 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/file_manipulation.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb @@ -18,12 +18,14 @@ class Thor # # copy_file "doc/README" # - def copy_file(source, destination=nil, config={}) + def copy_file(source, destination=nil, config={}, &block) destination ||= source source = File.expand_path(find_in_source_paths(source.to_s)) create_file destination, nil, config do - File.read(source) + content = File.read(source) + content = block.call(content) if block + content end end @@ -72,13 +74,15 @@ class Thor # # template "doc/README" # - def template(source, destination=nil, config={}) + def template(source, destination=nil, config={}, &block) destination ||= source source = File.expand_path(find_in_source_paths(source.to_s)) context = instance_eval('binding') create_file destination, nil, config do - ERB.new(::File.read(source), nil, '-').result(context) + content = ERB.new(::File.read(source), nil, '-').result(context) + content = block.call(content) if block + content end end diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb index 0636ec6591..6b0b42ea02 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/inject_into_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb @@ -11,7 +11,7 @@ class Thor # data<String>:: Data to add to the file. Can be given as a block. # config<Hash>:: give :verbose => false to not log the status and the flag # for injection (:after or :before). - # + # # ==== Examples # # inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n" diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb index 700d794123..700d794123 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/base.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb index 78bc5cf4bf..40d201d9e4 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -65,7 +65,7 @@ class Thor else self[$1] == args.first end - else + else self[method] end end diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb index 27fea5bb35..27fea5bb35 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/ordered_hash.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb index f9b31a35d1..f9b31a35d1 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/error.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb index 0964a9667a..021a067a3e 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/group.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb @@ -132,7 +132,7 @@ class Thor::Group names.each do |name| unless class_options.key?(name) - raise ArgumentError, "You have to define the option #{name.inspect} " << + raise ArgumentError, "You have to define the option #{name.inspect} " << "before setting invoke_from_option." end diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb index 32e6a72454..32e6a72454 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/invocation.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb index 57a3f6e1a5..57a3f6e1a5 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb index aa8ace4719..aa8ace4719 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/argument.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb index fb5d965e06..fb5d965e06 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/arguments.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb index 9e40ec73fa..e09b4901e2 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/option.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb @@ -36,7 +36,7 @@ class Thor # string (--foo=value) or booleans (just --foo). # # By default all options are optional, unless :required is given. - # + # def self.parse(key, value) if key.is_a?(Array) name, *aliases = key diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb index 75092308b5..75092308b5 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/options.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb index 0d0757fdda..0d0757fdda 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/rake_compat.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb index 9dc70ea069..079f9e0c65 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/runner.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb @@ -36,7 +36,7 @@ class Thor::Runner < Thor #:nodoc: def install(name) initialize_thorfiles - # If a directory name is provided as the argument, look for a 'main.thor' + # If a directory name is provided as the argument, look for a 'main.thor' # task in said directory. begin if File.directory?(File.expand_path(name)) diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb index 1dc8f0e5b4..1dc8f0e5b4 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/basic.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb index ea9665380b..f6be3575ca 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/basic.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb @@ -143,7 +143,7 @@ class Thor answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}] case answer - when is?(:yes), is?(:force) + when is?(:yes), is?(:force), "" return true when is?(:no), is?(:skip) return false diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/color.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb index 24704f7885..24704f7885 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/color.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/task.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb index 91c7564d3f..91c7564d3f 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/task.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/util.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb index ebae0a3193..ebae0a3193 100644 --- a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/util.rb +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb new file mode 100644 index 0000000000..650253d648 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb @@ -0,0 +1,3 @@ +class Thor + VERSION = "0.12.1".freeze +end diff --git a/railties/railties.gemspec b/railties/railties.gemspec new file mode 100644 index 0000000000..a060c3c301 --- /dev/null +++ b/railties/railties.gemspec @@ -0,0 +1,28 @@ +Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = 'railties' + s.version = '3.0.pre' + s.summary = "Controls boot-up, rake tasks and generators for the Rails framework." + s.description = <<-EOF + Rails is a framework for building web-application using CGI, FCGI, mod_ruby, or WEBrick + on top of either MySQL, PostgreSQL, SQLite, DB2, SQL Server, or Oracle with eRuby- or Builder-based templates. + EOF + + s.add_dependency('rake', '>= 0.8.3') + s.add_dependency('activesupport', '= 3.0.pre') + s.add_dependency('actionpack', '= 3.0.pre') + + s.rdoc_options << '--exclude' << '.' + s.has_rdoc = false + + s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'builtin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] + s.require_path = 'lib' + s.bindir = "bin" + s.executables = ["rails"] + s.default_executable = "rails" + + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://www.rubyonrails.org" + s.rubyforge_project = "rails" +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index a3e1916494..4d9da525d7 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -4,7 +4,16 @@ module ApplicationTests class InitializerTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def copy_app + FileUtils.cp_r(app_path, new_app) + end + def setup + FileUtils.rm_rf(new_app) if File.directory?(new_app) build_app boot_rails end @@ -15,34 +24,64 @@ module ApplicationTests end test "the application root can be set" do - FileUtils.mkdir_p("#{app_path}/hello") + copy_app add_to_config <<-RUBY - config.frameworks = [] - config.root = '#{app_path}/hello' + config.root = '#{new_app}' RUBY + + use_frameworks [] + + require "#{app_path}/config/environment" + assert_equal Pathname.new(new_app), Rails.application.root + end + + test "the application root is Dir.pwd if there is no config.ru" do + File.delete("#{app_path}/config.ru") + + use_frameworks [] + + Dir.chdir("#{app_path}") do + require "#{app_path}/config/environment" + assert_equal Pathname.new("#{app_path}"), Rails.application.root + end + end + + test "if there's no config.active_support.bare, all of ActiveSupport is required" do + use_frameworks [] require "#{app_path}/config/environment" - assert_equal Pathname.new("#{app_path}/hello"), Rails.application.root + assert_nothing_raised { [1,2,3].rand } + end + + test "config.active_support.bare does not require all of ActiveSupport" do + add_to_config "config.active_support.bare = true" + + use_frameworks [] + + Dir.chdir("#{app_path}/app") do + require "#{app_path}/config/environment" + assert_raises(NoMethodError) { [1,2,3].rand } + end end - test "the application root is detected as where config.ru is located" do + test "marking the application as threadsafe sets the correct config variables" do add_to_config <<-RUBY - config.frameworks = [] + config.threadsafe! RUBY - FileUtils.mv "#{app_path}/config.ru", "#{app_path}/config/config.ru" - require "#{app_path}/config/environment" - assert_equal Pathname.new("#{app_path}/config"), Rails.application.root + + require "#{app_path}/config/application" + assert AppTemplate::Application.config.action_controller.allow_concurrency end - test "the application root is Dir.pwd if there is no config.ru" do - File.delete("#{app_path}/config.ru") + test "the application can be marked as threadsafe when there are no frameworks" do + FileUtils.rm_rf("#{app_path}/config/environments") add_to_config <<-RUBY config.frameworks = [] + config.threadsafe! RUBY - Dir.chdir("#{app_path}/app") do - require "#{app_path}/config/environment" - assert_equal Pathname.new("#{app_path}/app"), Rails.application.root + assert_nothing_raised do + require "#{app_path}/config/application" end end end -end
\ No newline at end of file +end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index ccbcd84176..7b27c780aa 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -7,12 +7,20 @@ module ApplicationTests def setup build_app boot_rails + end + + def app_const + @app_const ||= Class.new(Rails::Application) + end + + def with_config require "rails" require "rails/generators" + yield app_const.config end test "generators default values" do - Rails::Initializer.run do |c| + with_config do |c| assert_equal(true, c.generators.colorize_logging) assert_equal({}, c.generators.aliases) assert_equal({}, c.generators.options) @@ -20,7 +28,7 @@ module ApplicationTests end test "generators set rails options" do - Rails::Initializer.run do |c| + with_config do |c| c.generators.orm = :datamapper c.generators.test_framework = :rspec c.generators.helper = false @@ -30,7 +38,7 @@ module ApplicationTests end test "generators set rails aliases" do - Rails::Initializer.run do |c| + with_config do |c| c.generators.aliases = { :rails => { :test_framework => "-w" } } expected = { :rails => { :test_framework => "-w" } } assert_equal expected, c.generators.aliases @@ -38,14 +46,15 @@ module ApplicationTests end test "generators aliases and options on initialization" do - Rails::Initializer.run do |c| - c.frameworks = [] - c.generators.rails :aliases => { :test_framework => "-w" } - c.generators.orm :datamapper - c.generators.test_framework :rspec - end + add_to_config <<-RUBY + config.generators.rails :aliases => { :test_framework => "-w" } + config.generators.orm :datamapper + config.generators.test_framework :rspec + RUBY + + require "#{app_path}/config/environment" # Initialize the application - Rails.initialize! + require "rails/generators" Rails::Generators.configure! assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] @@ -53,19 +62,20 @@ module ApplicationTests end test "generators no color on initialization" do - Rails::Initializer.run do |c| - c.frameworks = [] - c.generators.colorize_logging = false - end + add_to_config <<-RUBY + config.generators.colorize_logging = false + RUBY + # Initialize the application - Rails.initialize! + require "#{app_path}/config/environment" + require "rails/generators" Rails::Generators.configure! assert_equal Thor::Base.shell, Thor::Shell::Basic end test "generators with hashes for options and aliases" do - Rails::Initializer.run do |c| + with_config do |c| c.generators do |g| g.orm :datamapper, :migration => false g.plugin :aliases => { :generator => "-g" }, @@ -84,7 +94,7 @@ module ApplicationTests end test "generators with hashes are deep merged" do - Rails::Initializer.run do |c| + with_config do |c| c.generators do |g| g.orm :datamapper, :migration => false g.plugin :aliases => { :generator => "-g" }, diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb index 719520bf68..2ecc3e9e2d 100644 --- a/railties/test/application/initializer_test.rb +++ b/railties/test/application/initializer_test.rb @@ -10,42 +10,15 @@ module ApplicationTests require "rails" end - test "initializing an application initializes rails" do - Rails::Initializer.run do |config| - config.root = app_path - end - - if RUBY_VERSION < '1.9' - $KCODE = '' - Rails.initialize! - assert_equal 'UTF8', $KCODE - else - Encoding.default_external = Encoding::US_ASCII - Rails.initialize! - assert_equal Encoding::UTF_8, Encoding.default_external - end - end - test "initializing an application adds the application paths to the load path" do - Rails::Initializer.run do |config| - config.root = app_path - end + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY - Rails.initialize! + require "#{app_path}/config/environment" assert $:.include?("#{app_path}/app/models") end - test "adding an unknown framework raises an error" do - Rails::Initializer.run do |config| - config.root = app_path - config.frameworks << :action_foo - end - - assert_raises RuntimeError do - Rails.initialize! - end - end - test "eager loading loads parent classes before children" do app_file "lib/zoo.rb", <<-ZOO class Zoo ; include ReptileHouse ; end @@ -54,12 +27,12 @@ module ApplicationTests module Zoo::ReptileHouse ; end ZOO - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.eager_load_paths = "#{app_path}/lib" - end + RUBY - Rails.initialize! + require "#{app_path}/config/environment" assert Zoo end @@ -67,41 +40,43 @@ module ApplicationTests test "load environment with global" do app_file "config/environments/development.rb", "$initialize_test_set_from_env = 'success'" assert_nil $initialize_test_set_from_env - Rails::Initializer.run { |config| config.root = app_path } - Rails.initialize! + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + require "#{app_path}/config/environment" assert_equal "success", $initialize_test_set_from_env end test "action_controller load paths set only if action controller in use" do assert_nothing_raised NameError do - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.frameworks = [] - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" end end test "after_initialize block works correctly" do - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.after_initialize { $test_after_initialize_block1 = "success" } config.after_initialize { $test_after_initialize_block2 = "congratulations" } - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 end test "after_initialize block works correctly when no block is passed" do - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.after_initialize { $test_after_initialize_block1 = "success" } config.after_initialize # don't pass a block, this is what we're testing! config.after_initialize { $test_after_initialize_block2 = "congratulations" } - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 @@ -109,35 +84,40 @@ module ApplicationTests # i18n test "setting another default locale" do - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.i18n.default_locale = :de - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" assert_equal :de, I18n.default_locale end test "no config locales dir present should return empty load path" do FileUtils.rm_rf "#{app_path}/config/locales" - Rails::Initializer.run do |c| - c.root = app_path - assert_equal [], c.i18n.load_path - end + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + require "#{app_path}/config/environment" + + assert_equal [], Rails.application.config.i18n.load_path end test "config locales dir present should be added to load path" do - Rails::Initializer.run do |c| - c.root = app_path - assert_equal ["#{app_path}/config/locales/en.yml"], c.i18n.load_path - end + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + assert_equal ["#{app_path}/config/locales/en.yml"], Rails.application.config.i18n.load_path end test "config defaults should be added with config settings" do - Rails::Initializer.run do |c| - c.root = app_path - c.i18n.load_path << "my/other/locale.yml" - end + add_to_config <<-RUBY + config.root = "#{app_path}" + config.i18n.load_path << "my/other/locale.yml" + RUBY + require "#{app_path}/config/environment" assert_equal [ "#{app_path}/config/locales/en.yml", "my/other/locale.yml" @@ -146,64 +126,47 @@ module ApplicationTests # DB middleware test "database middleware doesn't initialize when session store is not active_record" do - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.action_controller.session_store = :cookie_store - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore) end - test "database middleware doesn't initialize when activerecord is not in frameworks" do - Rails::Initializer.run do |c| - c.root = app_path - c.frameworks = [] - end - assert_equal [], Rails.application.config.middleware - end - test "database middleware initializes when session store is active record" do - Rails::Initializer.run do |c| - c.root = app_path - c.action_controller.session_store = :active_record_store - end - Rails.initialize! + add_to_config "config.action_controller.session_store = :active_record_store" + + require "#{app_path}/config/environment" expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] middleware = Rails.application.config.middleware.map { |m| m.klass } assert_equal expects, middleware & expects end - test "ensure database middleware doesn't use action_controller on initializing" do - Rails::Initializer.run do |c| - c.root = app_path - c.frameworks -= [:action_controller] - c.action_controller.session_store = :active_record_store - end - Rails.initialize! - - assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore) + test "Rails.root should be a Pathname" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + require "#{app_path}/config/environment" + assert_instance_of Pathname, Rails.root end + end - # Pathview test - test "load view paths doesn't perform anything when action_view not in frameworks" do - Rails::Initializer.run do |c| - c.root = app_path - c.frameworks -= [:action_view] - end - Rails.initialize! + class InitializerCustomFrameworkExtensionsTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation - assert_equal nil, ActionMailer::Base.template_root - assert_equal [], ActionController::Base.view_paths + def setup + build_app + boot_rails end - test "Rails.root should be a Pathname" do - Rails::Initializer.run do |c| - c.root = app_path - end - Rails.initialize! - assert_instance_of Pathname, Rails.root + test "database middleware doesn't initialize when activerecord is not in frameworks" do + use_frameworks [] + require "#{app_path}/config/environment" + + assert !defined?(ActiveRecord) end end end
\ No newline at end of file diff --git a/railties/test/application/load_test.rb b/railties/test/application/load_test.rb index 3da51c4355..1c5811b07a 100644 --- a/railties/test/application/load_test.rb +++ b/railties/test/application/load_test.rb @@ -1,27 +1,13 @@ require "isolation/abstract_unit" -# require "rails" -# require 'action_dispatch' module ApplicationTests class LoadTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation def rackup - config = "#{app_path}/config.ru" - # Copied from ActionDispatch::Utils.parse_config - # ActionDispatch is not necessarily available at this point. - require 'rack' - if config =~ /\.ru$/ - cfgfile = ::File.read(config) - if cfgfile[/^#\\(.*)/] - opts.parse! $1.split(/\s+/) - end - inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", - nil, config - else - require config - inner_app = Object.const_get(::File.basename(config, '.rb').capitalize) - end + require "rack" + app, options = Rack::Builder.parse_file("#{app_path}/config.ru") + app end def setup @@ -34,20 +20,22 @@ module ApplicationTests end test "config.ru can be racked up" do - @app = rackup - assert_welcome get("/") + Dir.chdir app_path do + @app = rackup + assert_welcome get("/") + end end test "Rails.application is available after config.ru has been racked up" do rackup - assert Rails.application < Rails::Application + assert Rails.application.is_a?(Rails::Application) end # Passenger still uses AC::Dispatcher, so we need to # keep it working for now test "deprecated ActionController::Dispatcher still works" do rackup - assert ActionController::Dispatcher.new < Rails::Application + assert ActionController::Dispatcher.new.is_a?(Rails::Application) end test "the config object is available on the application object" do diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb index 62ed4f4ad4..71e406f2c1 100644 --- a/railties/test/application/notifications_test.rb +++ b/railties/test/application/notifications_test.rb @@ -5,19 +5,8 @@ module ApplicationTests include ActiveSupport::Testing::Isolation class MyQueue - attr_reader :events, :subscribers - - def initialize - @events = [] - @subscribers = [] - end - def publish(name, *args) - @events << name - end - - def subscribe(pattern=nil, &block) - @subscribers << pattern + raise name end end @@ -26,21 +15,16 @@ module ApplicationTests boot_rails require "rails" require "active_support/notifications" + @events = [] Rails::Initializer.run do |c| - c.notifications.queue = MyQueue.new - c.notifications.subscribe(/listening/) do - puts "Cool" - end + c.notifications.notifier = ActiveSupport::Notifications::Notifier.new(MyQueue.new) end end test "new queue is set" do - ActiveSupport::Notifications.instrument(:foo) - assert_equal :foo, ActiveSupport::Notifications.queue.events.first - end - - test "configuration subscribers are loaded" do - assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == /listening/ } + assert_raise RuntimeError do + ActiveSupport::Notifications.publish('foo') + end end end end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb new file mode 100644 index 0000000000..725dd06929 --- /dev/null +++ b/railties/test/application/routing_test.rb @@ -0,0 +1,180 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class RoutingTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + require 'rack/test' + extend Rack::Test::Methods + end + + def app + @app ||= begin + require "#{app_path}/config/environment" + + Rails.application + end + end + + test "rails/info/properties" do + get "/rails/info/properties" + assert_equal 200, last_response.status + end + + test "simple controller" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + end + + test "multiple controllers" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + render :text => "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ActionController::Base + def index + render :text => "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/bar' + assert_equal 'bar', last_response.body + end + + test "nested controller" do + controller 'foo', <<-RUBY + class FooController < ActionController::Base + def index + render :text => "foo" + end + end + RUBY + + controller 'admin/foo', <<-RUBY + module Admin + class FooController < ActionController::Base + def index + render :text => "admin::foo" + end + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/admin/foo' + assert_equal 'admin::foo', last_response.body + end + + test "merges with plugin routes" do + controller 'foo', <<-RUBY + class FooController < ActionController::Base + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => 'foo#index' + end + RUBY + + plugin 'bar', 'require File.dirname(__FILE__) + "/app/controllers/bar"' do |plugin| + plugin.write 'app/controllers/bar.rb', <<-RUBY + class BarController < ActionController::Base + def index + render :text => "bar" + end + end + RUBY + + plugin.write 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'bar', :to => 'bar#index' + end + RUBY + end + + get '/foo' + assert_equal 'foo', last_response.body + + get '/bar' + assert_equal 'bar', last_response.body + end + + test "reloads routes when configuration is changed" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def bar + render :text => "bar" + end + + def baz + render :text => "baz" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => 'foo#bar' + end + RUBY + + get '/foo' + assert_equal 'bar', last_response.body + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => 'foo#baz' + end + RUBY + + sleep 0.1 + + get '/foo' + assert_equal 'baz', last_response.body + end + end +end diff --git a/railties/test/fixtures/plugins/engines/engine/config/routes.rb b/railties/test/fixtures/plugins/engines/engine/config/routes.rb index cca8d1b146..da44595693 100644 --- a/railties/test/fixtures/plugins/engines/engine/config/routes.rb +++ b/railties/test/fixtures/plugins/engines/engine/config/routes.rb @@ -1,3 +1,3 @@ ActionController::Routing::Routes.draw do |map| - map.connect '/engine', :controller => "engine" + match '/engine', :to => "engine" end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 7d03a37f2a..b69f23c965 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -171,7 +171,7 @@ class ActionsTest < GeneratorsTestCase def test_route_should_add_data_to_the_routes_block_in_config_routes run_generator - route_command = "map.route '/login', :controller => 'sessions', :action => 'new'" + route_command = "route '/login', :controller => 'sessions', :action => 'new'" action :route, route_command assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/ end diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 886af01b22..dff3908ea1 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -62,7 +62,7 @@ class ResourceGeneratorTest < GeneratorsTestCase run_generator assert_file "config/routes.rb" do |route| - assert_match /map\.resources :accounts$/, route + assert_match /resources :accounts$/, route end end @@ -70,7 +70,7 @@ class ResourceGeneratorTest < GeneratorsTestCase run_generator ["account", "--singleton"] assert_file "config/routes.rb" do |route| - assert_match /map\.resource :account$/, route + assert_match /resource :account$/, route end end @@ -93,7 +93,7 @@ class ResourceGeneratorTest < GeneratorsTestCase run_generator ["account"], :behavior => :revoke assert_file "config/routes.rb" do |route| - assert_no_match /map\.resources :accounts$/, route + assert_no_match /resources :accounts$/, route end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index c0652c034f..0b961cee19 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -25,7 +25,7 @@ class ScaffoldGeneratorTest < GeneratorsTestCase # Route assert_file "config/routes.rb" do |route| - assert_match /map\.resources :product_lines$/, route + assert_match /resources :product_lines$/, route end # Controller @@ -99,7 +99,7 @@ class ScaffoldGeneratorTest < GeneratorsTestCase # Route assert_file "config/routes.rb" do |route| - assert_no_match /map\.resources :product_lines$/, route + assert_no_match /resources :product_lines$/, route end # Controller diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index a9e60680ac..e308cbcb0e 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -150,6 +150,16 @@ module InitializableTests Word.run_initializers assert_equal "bird", $word end + + test "creating initializer without a block raises an error" do + assert_raise(ArgumentError) do + Class.new do + include Rails::Initializable + + initializer :foo + end + end + end end class BeforeAfter < ActiveSupport::TestCase diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb index 97d884e1be..0691caad9d 100644 --- a/railties/test/initializer/check_ruby_version_test.rb +++ b/railties/test/initializer/check_ruby_version_test.rb @@ -7,7 +7,6 @@ module InitializerTests def setup build_app boot_rails - require "rails" end test "rails does not initialize with ruby version 1.8.1" do @@ -50,8 +49,7 @@ module InitializerTests def assert_rails_boots(version = nil) set_ruby_version(version) if version assert_nothing_raised "It appears that rails does not boot" do - Rails::Initializer.run { |c| c.frameworks = [] } - Rails.initialize! + require "rails" end end @@ -59,8 +57,7 @@ module InitializerTests set_ruby_version(version) $stderr = File.open("/dev/null", "w") assert_raises(SystemExit) do - Rails::Initializer.run { |c| c.frameworks = [] } - Rails.initialize! + require "rails" end end end diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb index 076816d73b..472566378d 100644 --- a/railties/test/initializer/initialize_i18n_test.rb +++ b/railties/test/initializer/initialize_i18n_test.rb @@ -7,16 +7,15 @@ module InitializerTests def setup build_app boot_rails - require "rails" end # test_config_defaults_and_settings_should_be_added_to_i18n_defaults test "i18n config defaults and settings should be added to i18n defaults" do - Rails::Initializer.run do |c| - c.root = app_path - c.i18n.load_path << "my/other/locale.yml" - end - Rails.initialize! + add_to_config <<-RUBY + config.root = "#{app_path}" + config.i18n.load_path << "my/other/locale.yml" + RUBY + require "#{app_path}/config/environment" #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml assert_equal %W( diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb index 1b58a58555..fa66ebcd83 100644 --- a/railties/test/initializer/path_test.rb +++ b/railties/test/initializer/path_test.rb @@ -7,14 +7,14 @@ class PathsTest < Test::Unit::TestCase build_app boot_rails require "rails" - Rails::Initializer.run do |config| - config.root = app_path + add_to_config <<-RUBY + config.root = "#{app_path}" config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record] config.after_initialize do ActionController::Base.session_store = nil end - end - Rails.initialize! + RUBY + require "#{app_path}/config/environment" @paths = Rails.application.config.paths end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index ba8b35d5cc..ee0a812b47 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -89,6 +89,13 @@ module TestHelpers end end + routes = File.read("#{app_path}/config/routes.rb") + if routes =~ /(\n\s*end\s*)\Z/ + File.open("#{app_path}/config/routes.rb", 'w') do |f| + f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)'\n" + $1 + end + end + add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' end @@ -128,7 +135,7 @@ module TestHelpers def add_to_config(str) environment = File.read("#{app_path}/config/application.rb") - if environment =~ /(\n\s*end\s*)\Z/ + if environment =~ /(\n\s*end\s*end\s*)\Z/ File.open("#{app_path}/config/application.rb", 'w') do |f| f.puts $` + "\n#{str}\n" + $1 end @@ -146,6 +153,14 @@ module TestHelpers app_file("app/controllers/#{name}_controller.rb", contents) end + def use_frameworks(arr) + to_remove = [:actionmailer, + :activemodel, + :activerecord, + :activeresource] - arr + $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' } + end + def boot_rails root = File.expand_path('../../../..', __FILE__) begin diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index c724799d64..d60d6177f6 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -2,7 +2,6 @@ require 'abstract_unit' require 'rails/paths' class PathsTest < ActiveSupport::TestCase - def setup @root = Rails::Application::Root.new("/foo/bar") end @@ -228,4 +227,4 @@ class PathsTest < ActiveSupport::TestCase @root.app.eager_load! assert_equal ["/foo/bar/app"], @root.load_paths end -end
\ No newline at end of file +end diff --git a/railties/test/plugins/configuration_test.rb b/railties/test/plugins/configuration_test.rb new file mode 100644 index 0000000000..5786316d1d --- /dev/null +++ b/railties/test/plugins/configuration_test.rb @@ -0,0 +1,36 @@ +require "isolation/abstract_unit" + +module PluginsTest + class ConfigurationTest < Test::Unit::TestCase + def setup + build_app + boot_rails + require "rails" + end + + test "config is available to plugins" do + class Foo < Rails::Plugin ; end + assert_nil Foo.config.action_controller.foo + end + + test "a config name is available for the plugin" do + class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end + assert_equal "hello", Foo.config.foo.greetings + end + + test "plugin configurations are available in the application" do + class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end + require "#{app_path}/config/application" + assert_equal "hello", AppTemplate::Application.config.foo.greetings + end + + test "plugin config merges are deep" do + class Foo < Rails::Plugin ; config.foo.greetings = 'hello' ; end + class MyApp < Rails::Application + config.foo.bar = "bar" + end + assert_equal "hello", MyApp.config.foo.greetings + assert_equal "bar", MyApp.config.foo.bar + end + end +end diff --git a/railties/test/plugins/framework_extension_test.rb b/railties/test/plugins/framework_extension_test.rb new file mode 100644 index 0000000000..87e19cadce --- /dev/null +++ b/railties/test/plugins/framework_extension_test.rb @@ -0,0 +1,18 @@ +require "isolation/abstract_unit" + +module PluginsTest + class FrameworkExtensionTest < Test::Unit::TestCase + def setup + build_app + boot_rails + end + + test "active_record extensions are applied to ActiveRecord" do + add_to_config "config.active_record.table_name_prefix = 'tbl_'" + + require "#{app_path}/config/environment" + + assert_equal 'tbl_', ActiveRecord::Base.table_name_prefix + end + 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 a0484c0868..435bd34925 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'action_controller' +require 'action_controller/test_case' require 'rails/info' require 'rails/info_controller' @@ -14,17 +15,12 @@ class InfoControllerTest < ActionController::TestCase tests Rails::InfoController def setup - ActionController::Routing.use_controllers!(['rails/info']) ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' + match ':controller/:action' end @controller.stubs(:consider_all_requests_local => false, :local_request? => true) end - def teardown - ActionController::Routing.use_controllers! nil - end - test "info controller does not allow remote requests" do @controller.stubs(:consider_all_requests_local => false, :local_request? => false) get :properties |