diff options
author | José Valim <jose.valim@gmail.com> | 2009-11-02 23:46:00 -0200 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2009-11-02 23:46:00 -0200 |
commit | d002826e54415a340e55fdbf363d005faebf8fc5 (patch) | |
tree | 431146dfb0b15c4507fd49bb7d6f18bd40ccf839 | |
parent | 9ba83cce0318fa5051764f4a16c286adf30169e2 (diff) | |
parent | 14370e1aab6ddfb5b86cf50bd7e5abcebae0684c (diff) | |
download | rails-d002826e54415a340e55fdbf363d005faebf8fc5.tar.gz rails-d002826e54415a340e55fdbf363d005faebf8fc5.tar.bz2 rails-d002826e54415a340e55fdbf363d005faebf8fc5.zip |
Merge branch 'master' of git://github.com/rails/rails
447 files changed, 6360 insertions, 7603 deletions
diff --git a/.gitignore b/.gitignore index da296e7e11..3b922f29f7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ railties/guides/output *.rbc *.swp *.swo -actionpack/bin +bin vendor/gems/ -*/vendor/gems/ railties/tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..e99e7c7591 --- /dev/null +++ b/Gemfile @@ -0,0 +1,21 @@ +Gem.sources.each { |uri| source uri } + +gem "rails", "3.0.pre", :vendored_at => "railties" +%w( + activesupport + activemodel + actionpack + actionmailer + activerecord + activeresource +).each do |lib| + gem lib, '3.0.pre', :vendored_at => lib +end +gem "rack", "1.0.1" +gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" +gem "rack-test", "~> 0.5.0" +gem "erubis", "~> 2.6.0" +gem "arel", :git => "git://github.com/rails/arel.git" +gem "mocha" +gem "sqlite3-ruby" +gem "RedCloth" diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index a756f35ee8..96c84b986e 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -44,25 +44,24 @@ Rake::RDocTask.new { |rdoc| rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/action_mailer.rb') rdoc.rdoc_files.include('lib/action_mailer/*.rb') + rdoc.rdoc_files.include('lib/action_mailer/delivery_method/*.rb') } spec = eval(File.read('actionmailer.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end desc "Publish the API documentation" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index a427376579..23f04a11ba 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -32,8 +32,9 @@ module ActionMailer end autoload :AdvAttrAccessor, 'action_mailer/adv_attr_accessor' + autoload :DeprecatedBody, 'action_mailer/deprecated_body' autoload :Base, 'action_mailer/base' - autoload :Helpers, 'action_mailer/helpers' + autoload :DeliveryMethod, 'action_mailer/delivery_method' autoload :Part, 'action_mailer/part' autoload :PartContainer, 'action_mailer/part_container' autoload :Quoting, 'action_mailer/quoting' diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index c0c04af51c..293af01bbf 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,8 +1,4 @@ -require 'tmpdir' - -require "active_support/core_ext/class" -# Use the old layouts until actionmailer gets refactored -require "action_controller/legacy/layout" +require 'active_support/core_ext/class' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -232,7 +228,7 @@ module ActionMailer #:nodoc: # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered. # # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:test</tt>, - # and <tt>:file</tt>. + # and <tt>:file</tt>. Or you may provide a custom delivery method object eg. MyOwnDeliveryMethodClass.new # # * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are, # but this can be turned off to help functional testing. @@ -256,13 +252,20 @@ module ActionMailer #:nodoc: # +implicit_parts_order+. class Base include AdvAttrAccessor, PartContainer, Quoting, Utils - extend AbstractController::RenderingController + + include AbstractController::RenderingController + include AbstractController::LocalizedCache + include AbstractController::Layouts + + include AbstractController::Helpers + helper MailHelper if Object.const_defined?(:ActionController) include ActionController::UrlWriter - include ActionController::Layout end + include ActionMailer::DeprecatedBody + private_class_method :new #:nodoc: class_inheritable_accessor :view_paths @@ -270,35 +273,9 @@ module ActionMailer #:nodoc: cattr_accessor :logger - @@smtp_settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true, - } - cattr_accessor :smtp_settings - - @@sendmail_settings = { - :location => '/usr/sbin/sendmail', - :arguments => '-i -t' - } - cattr_accessor :sendmail_settings - - @@file_settings = { - :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" - } - - cattr_accessor :file_settings - @@raise_delivery_errors = true cattr_accessor :raise_delivery_errors - superclass_delegating_accessor :delivery_method - self.delivery_method = :smtp - @@perform_deliveries = true cattr_accessor :perform_deliveries @@ -317,17 +294,12 @@ module ActionMailer #:nodoc: @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ] cattr_accessor :default_implicit_parts_order + @@protected_instance_variables = [] cattr_reader :protected_instance_variables - @@protected_instance_variables = %w(@body) # Specify the BCC addresses for the message adv_attr_accessor :bcc - # Define the body of the message. This is either a Hash (in which case it - # specifies the variables to pass to the template when it is rendered), - # or a string, in which case it specifies the actual text of the message. - adv_attr_accessor :body - # Specify the CC addresses for the message. adv_attr_accessor :cc @@ -372,43 +344,42 @@ 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 + def mailer_name(value = nil) if value - self.mailer_name = value + @mailer_name = value else - self.class.mailer_name + @mailer_name || self.class.mailer_name end end - def mailer_name=(value) - self.class.mailer_name = value - end - - # The mail object instance referenced by this mailer. - attr_reader :mail - attr_reader :template_name, :default_template_name, :action_name - - def controller_path - self.class.controller_path - end - - def formats - @template.formats - end + # Alias controller_path to mailer_name so render :partial in views work. + alias :controller_path :mailer_name class << self attr_writer :mailer_name + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::File, :prefix => :file + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Sendmail, :prefix => :sendmail + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Smtp, :prefix => :smtp + def mailer_name @mailer_name ||= name.underscore end - # for ActionView compatibility - alias_method :controller_name, :mailer_name - alias_method :controller_path, :mailer_name + def delivery_method=(method_name) + @delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name) + end def respond_to?(method_symbol, include_private = false) #:nodoc: matches_dynamic_method?(method_symbol) || super @@ -459,6 +430,7 @@ module ActionMailer #:nodoc: self.view_paths && self.view_paths.first end + # Should template root overwrite the whole view_paths? def template_root=(root) self.view_paths = ActionView::Base.process_view_paths(root) end @@ -470,6 +442,11 @@ module ActionMailer #:nodoc: end end + # Configure delivery method. Check ActionMailer::DeliveryMethod for more + # instructions. + superclass_delegating_reader :delivery_method + self.delivery_method = :smtp + # 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" @@ -481,62 +458,18 @@ module ActionMailer #:nodoc: # Initialize the mailer via the given +method_name+. The body will be # rendered and a new TMail::Mail object created. def create!(method_name, *parameters) #:nodoc: - ActiveSupport::Orchestra.instrument(:create_mail, :name => method_name) do - initialize_defaults(method_name) - __send__(method_name, *parameters) - - # If an explicit, textual body has not been set, we check assumptions. - unless String === @body - # First, we look to see if there are any likely templates that match, - # which include the content-type in their file name (i.e., - # "the_template_file.text.html.erb", etc.). Only do this if parts - # have not already been specified manually. - # if @parts.empty? - template_root.find_all(@template, {}, template_path).each do |template| - @parts << Part.new( - :content_type => template.mime_type ? template.mime_type.to_s : "text/plain", - :disposition => "inline", - :charset => charset, - :body => render_template(template, @body) - ) - end - - if @parts.size > 1 - @content_type = "multipart/alternative" if @content_type !~ /^multipart/ - @parts = sort_parts(@parts, @implicit_parts_order) - end - # end - - # Then, if there were such templates, we check to see if we ought to - # also render a "normal" template (without the content type). If a - # normal template exists (or if there were no implicit parts) we render - # it. - # ==== - # TODO: Revisit this - # template_exists = @parts.empty? - # template_exists ||= template_root.find("#{mailer_name}/#{@template}") - # @body = render_message(@template, @body) if template_exists - - # Finally, if there are other message parts and a textual body exists, - # we shift it onto the front of the parts and set the body to nil (so - # that create_mail doesn't try to render it in addition to the parts). - # ==== - # TODO: Revisit this - # if !@parts.empty? && String === @body - # @parts.unshift Part.new(:charset => charset, :body => @body) - # @body = nil - # end - end + initialize_defaults(method_name) + __send__(method_name, *parameters) - # If this is a multipart e-mail add the mime_version if it is not - # already set. - @mime_version ||= "1.0" if !@parts.empty? + # Create e-mail parts + create_parts - # build the mail object itself - @mail = create_mail - end + # Set the subject if not set yet + @subject ||= I18n.t(method_name, :scope => [:actionmailer, :subjects, mailer_name], + :default => method_name.humanize) - @mail + # build the mail object itself + @mail = create_mail end # Delivers a TMail::Mail object. By default, it delivers the cached mail @@ -545,14 +478,14 @@ module ActionMailer #:nodoc: def deliver!(mail = @mail) raise "no mail object available for delivery!" unless mail - unless logger.nil? + if logger logger.info "Sent mail to #{Array(recipients).join(', ')}" logger.debug "\n#{mail.encoded}" end - ActiveSupport::Orchestra.instrument(:deliver_mail, :mail => @mail) do + ActiveSupport::Notifications.instrument(:deliver_mail, :mail => @mail) do begin - __send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries + self.delivery_method.perform_delivery(mail) if perform_deliveries rescue Exception => e # Net::SMTP errors or sendmail pipe errors raise e if raise_delivery_errors end @@ -562,94 +495,61 @@ module ActionMailer #:nodoc: end private + # 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) - @charset ||= @@default_charset.dup - @content_type ||= @@default_content_type.dup + @charset ||= @@default_charset.dup + @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup - @template ||= method_name - @default_template_name = @action_name = @template - @mailer_name ||= self.class.name.underscore - @parts ||= [] + @mime_version ||= @@default_mime_version.dup if @@default_mime_version + + @mailer_name ||= self.class.mailer_name + @template ||= method_name + @action_name = @template + + @parts ||= [] @headers ||= {} - @body ||= {} - @mime_version = @@default_mime_version.dup if @@default_mime_version @sent_on ||= Time.now - end - def render_template(template, body) - if template.respond_to?(:mime_type) - @current_template_content_type = template.mime_type && template.mime_type.to_sym.to_s - end - - @template = initialize_template_class(body) - layout = _pick_layout(layout, true) unless - ActionController::Base.exempt_from_layout.include?(template.handler) - @template._render_template(template, layout, {}) - ensure - @current_template_content_type = nil + super # Run deprecation hooks end - def render_message(method_name, body) - render :file => method_name, :body => body - ensure - @current_template_content_type = nil - end + def create_parts + super # Run deprecation hooks - def render(opts) - opts[:locals] ||= {} - layout, file = opts.delete(:layout), opts[:file] - - begin - @template = initialize_template_class(opts.delete(:body)) - - if file - prefix = mailer_name unless file =~ /\// - template = view_paths.find(file, {:formats => formats}, prefix) + if String === response_body + @parts.unshift Part.new( + :content_type => "text/plain", + :disposition => "inline", + :charset => charset, + :body => response_body + ) + else + self.class.template_root.find_all(@template, {}, mailer_name).each do |template| + @parts << Part.new( + :content_type => template.mime_type ? template.mime_type.to_s : "text/plain", + :disposition => "inline", + :charset => charset, + :body => render_to_body(:_template => template) + ) end - layout = _pick_layout(layout, - !template || ActionController::Base.exempt_from_layout.include?(template.handler)) - - if template - @template._render_template(template, layout, opts) - elsif inline = opts[:inline] - @template._render_inline(inline, layout, opts) + if @parts.size > 1 + @content_type = "multipart/alternative" if @content_type !~ /^multipart/ + @parts = sort_parts(@parts, @implicit_parts_order) end - end - end - def default_template_format - if @current_template_content_type - Mime::Type.lookup(@current_template_content_type).to_sym - else - :html + # If this is a multipart e-mail add the mime_version if it is not + # already set. + @mime_version ||= "1.0" if !@parts.empty? end end - def template_root - self.class.template_root - end - - def template_root=(root) - self.class.template_root = root - end - - def template_path - "#{mailer_name}" - end - - def initialize_template_class(assigns) - template = ActionView::Base.new(self.class.view_paths, assigns, self) - template.formats = [default_template_format] - template - end - def sort_parts(parts, order = []) order = order.collect { |s| s.downcase } - + parts = parts.sort do |a, b| a_ct = a.content_type.downcase b_ct = b.content_type.downcase @@ -698,14 +598,6 @@ module ActionMailer #:nodoc: m.set_content_type(real_content_type, nil, ctype_attrs) m.body = normalize_new_lines(@parts.first.body) else - if String === body - part = TMail::Mail.new - part.body = normalize_new_lines(body) - part.set_content_type(real_content_type, nil, ctype_attrs) - part.set_content_disposition "inline" - m.parts << part - end - @parts.each do |p| part = (TMail::Mail === p ? p : p.to_mail(self)) m.parts << part @@ -720,43 +612,5 @@ module ActionMailer #:nodoc: @mail = m end - def perform_delivery_smtp(mail) - destinations = mail.destinations - mail.ready_to_send - sender = (mail['return-path'] && mail['return-path'].spec) || mail['from'] - - smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port]) - smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) - smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password], - smtp_settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, sender, destinations) - end - end - - def perform_delivery_sendmail(mail) - sendmail_args = sendmail_settings[:arguments] - sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] - IO.popen("#{sendmail_settings[:location]} #{sendmail_args}","w+") do |sm| - sm.print(mail.encoded.gsub(/\r/, '')) - sm.flush - end - end - - def perform_delivery_test(mail) - deliveries << mail - end - - def perform_delivery_file(mail) - FileUtils.mkdir_p file_settings[:location] - - (mail.to + mail.cc + mail.bcc).uniq.each do |to| - File.open(File.join(file_settings[:location], to), 'a') { |f| f.write(mail) } - end - end - end - - Base.class_eval do - include Helpers - helper MailHelper end end diff --git a/actionmailer/lib/action_mailer/delivery_method.rb b/actionmailer/lib/action_mailer/delivery_method.rb new file mode 100644 index 0000000000..29a51afdc3 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method.rb @@ -0,0 +1,57 @@ +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' + autoload :Test, 'action_mailer/delivery_method/test' + + # Creates a new DeliveryMethod object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActionMailer::DeliveryMethod::Stmp object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding + # delivery method class under the ActionMailer::DeliveryMethod namespace + # will be created. + # For example: + # + # ActionMailer::DeliveryMethod.lookup_method(:sendmail) + # # => returns a new ActionMailer::DeliveryMethod::Sendmail object + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActionMailer::DeliveryMethod.lookup_method(MyOwnDeliveryMethod.new) + # # => returns MyOwnDeliveryMethod.new + def self.lookup_method(delivery_method) + case delivery_method + when Symbol + method_name = delivery_method.to_s.camelize + method_class = ActionMailer::DeliveryMethod.const_get(method_name) + method_class.new + when nil # default + Smtp.new + else + delivery_method + end + end + + # An abstract delivery method class. There are multiple delivery method classes. + # See the classes under the ActionMailer::DeliveryMethod, e.g. + # ActionMailer::DeliveryMethod::Smtp. + # Smtp is the default delivery method for production + # while Test is used in testing. + # + # each delivery method exposes just one method + # + # delivery_method = ActionMailer::DeliveryMethod::Smtp.new + # delivery_method.perform_delivery(mail) # send the mail via smtp + # + class Method + 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 new file mode 100644 index 0000000000..587ae37ffa --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/file.rb @@ -0,0 +1,21 @@ +require 'tmpdir' + +module ActionMailer + module DeliveryMethod + + # 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" + } + + def perform_delivery(mail) + FileUtils.mkdir_p settings[:location] + + (mail.to + mail.cc + mail.bcc).uniq.each do |to| + ::File.open(::File.join(settings[:location], to), 'a') { |f| f.write(mail) } + end + end + end + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/sendmail.rb b/actionmailer/lib/action_mailer/delivery_method/sendmail.rb new file mode 100644 index 0000000000..db55af79f1 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/sendmail.rb @@ -0,0 +1,22 @@ +module ActionMailer + module DeliveryMethod + + # A delivery method implementation which sends via sendmail. + class Sendmail < Method + self.settings = { + :location => '/usr/sbin/sendmail', + :arguments => '-i -t' + } + + def perform_delivery(mail) + sendmail_args = settings[:arguments] + sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] + IO.popen("#{settings[:location]} #{sendmail_args}","w+") do |sm| + sm.print(mail.encoded.gsub(/\r/, '')) + sm.flush + end + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/smtp.rb b/actionmailer/lib/action_mailer/delivery_method/smtp.rb new file mode 100644 index 0000000000..86b0ae8329 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/smtp.rb @@ -0,0 +1,31 @@ +module ActionMailer + module DeliveryMethod + # A delivery method implementation which sends via smtp. + class Smtp < Method + + self.settings = { + :address => "localhost", + :port => 25, + :domain => 'localhost.localdomain', + :user_name => nil, + :password => nil, + :authentication => nil, + :enable_starttls_auto => true, + } + + def perform_delivery(mail) + destinations = mail.destinations + mail.ready_to_send + sender = (mail['return-path'] && mail['return-path'].spec) || mail['from'] + + smtp = Net::SMTP.new(settings[:address], settings[:port]) + smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) + smtp.start(settings[:domain], settings[:user_name], settings[:password], + settings[:authentication]) do |smtp| + smtp.sendmail(mail.encoded, sender, destinations) + end + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/test.rb b/actionmailer/lib/action_mailer/delivery_method/test.rb new file mode 100644 index 0000000000..6e3239d52a --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/test.rb @@ -0,0 +1,12 @@ +module ActionMailer + module DeliveryMethod + + # A delivery method implementation designed for testing, which just appends each record to the :deliveries array + class Test < Method + def perform_delivery(mail) + ActionMailer::Base.deliveries << mail + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/deprecated_body.rb b/actionmailer/lib/action_mailer/deprecated_body.rb new file mode 100644 index 0000000000..50ff262432 --- /dev/null +++ b/actionmailer/lib/action_mailer/deprecated_body.rb @@ -0,0 +1,44 @@ +module ActionMailer + # TODO Remove this module all together in a next release. Ensure that super + # hooks in ActionMailer::Base are removed as well. + module DeprecatedBody + def self.included(base) + base.class_eval do + # Define the body of the message. This is either a Hash (in which case it + # specifies the variables to pass to the template when it is rendered), + # or a string, in which case it specifies the actual text of the message. + adv_attr_accessor :body + end + end + + def initialize_defaults(method_name) + @body ||= {} + end + + def create_parts + if String === @body + ActiveSupport::Deprecation.warn('body is deprecated. To set the body with a text ' << + 'call render(:text => "body").', caller[0,10]) + self.response_body = @body + elsif @body.is_a?(Hash) && !@body.empty? + ActiveSupport::Deprecation.warn('body is deprecated. To set assigns simply ' << + 'use instance variables', caller[0,10]) + @body.each { |k, v| instance_variable_set(:"@#{k}", v) } + end + end + + def render(*args) + options = args.last.is_a?(Hash) ? args.last : {} + if options[:body] + ActiveSupport::Deprecation.warn(':body is deprecated. To set assigns simply ' << + 'use instance variables', caller[0,1]) + + options.delete(:body).each do |k, v| + instance_variable_set(:"@#{k}", v) + end + end + + super + end + end +end diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb deleted file mode 100644 index ecd8f0f5b6..0000000000 --- a/actionmailer/lib/action_mailer/helpers.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'active_support/dependencies' - -module ActionMailer - module Helpers #:nodoc: - def self.included(base) #:nodoc: - # Initialize the base module to aggregate its helpers. - base.class_inheritable_accessor :master_helper_module - base.master_helper_module = Module.new - - # Extend base with class methods to declare helpers. - base.extend(ClassMethods) - - base.class_eval do - # Wrap inherited to create a new master helper module for subclasses. - class << self - alias_method_chain :inherited, :helper - end - - # Wrap initialize_template_class to extend new template class - # instances with the master helper module. - alias_method_chain :initialize_template_class, :helper - end - end - - module ClassMethods - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(helper_module) #:nodoc: - master_helper_module.module_eval "include #{helper_module}" - end - - # Declare a helper: - # helper :foo - # requires 'foo_helper' and includes FooHelper in the template class. - # helper FooHelper - # includes FooHelper in the template class. - # helper { def foo() "#{bar} is the very best" end } - # evaluates the block in the template class, adding method +foo+. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # does all three. - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - require_dependency(file_name, "Missing helper file helpers/%s.rb") - # begin - # require_dependency(file_name) - # rescue LoadError => load_error - # requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] - # msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" - # raise LoadError.new(msg).copy_blame!(load_error) - # end - - add_template_helper(class_name.constantize) - else - raise ArgumentError, 'helper expects String, Symbol, or Module argument' - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declare a controller method as a helper. For example, - # helper_method :link_to - # def link_to(name, options) ... end - # makes the link_to controller method available in the view. - def helper_method(*methods) - methods.flatten.each do |method| - master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) - controller.__send__(%(#{method}), *args, &block) - end - end_eval - end - end - - # Declare a controller attribute as a helper. For example, - # helper_attr :name - # attr_accessor :name - # makes the name and name= controller methods available in the view. - # The is a convenience wrapper for helper_method. - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - private - def inherited_with_helper(child) - inherited_without_helper(child) - begin - child.master_helper_module = Module.new - child.master_helper_module.__send__(:include, master_helper_module) - child.helper child.name.to_s.underscore - rescue MissingSourceFile => e - raise unless e.is_missing?("#{child.name.to_s.underscore}_helper") - end - end - end - - private - # Extend the template class instance with our controller's helper module. - def initialize_template_class_with_helper(assigns) - initialize_template_class_without_helper(assigns).tap do |template| - template.extend self.class.master_helper_module - end - end - end -end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 7843623996..fcbaa9e186 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,15 +1,15 @@ -bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment" -if File.exist?("#{bundled}.rb") - require bundled -else - $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" - $:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib" +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/actionpack/lib") + $:.unshift("#{root}/actionmailer/lib") end require 'rubygems' require 'test/unit' -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'action_mailer' require 'action_mailer/test_case' diff --git a/actionmailer/test/delivery_method_test.rb b/actionmailer/test/delivery_method_test.rb index 1b8c3ba523..8f8c6b0275 100644 --- a/actionmailer/test/delivery_method_test.rb +++ b/actionmailer/test/delivery_method_test.rb @@ -11,6 +11,21 @@ class FileDeliveryMethodMailer < ActionMailer::Base self.delivery_method = :file end +class CustomDeliveryMethod + attr_accessor :custom_deliveries + def initialize() + @customer_deliveries = [] + end + + def self.perform_delivery(mail) + self.custom_deliveries << mail + end +end + +class CustomerDeliveryMailer < ActionMailer::Base + self.delivery_method = CustomDeliveryMethod.new +end + class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase def setup set_delivery_method :smtp @@ -21,7 +36,7 @@ class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_default_smtp - assert_equal :smtp, ActionMailer::Base.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Smtp, ActionMailer::Base.delivery_method end end @@ -35,7 +50,7 @@ class DefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_default_smtp - assert_equal :smtp, DefaultDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Smtp, DefaultDeliveryMethodMailer.delivery_method end end @@ -49,7 +64,7 @@ class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_set_delivery_method - assert_equal :sendmail, NonDefaultDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Sendmail, NonDefaultDeliveryMethodMailer.delivery_method end end @@ -63,7 +78,7 @@ class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_set_delivery_method - assert_equal :file, FileDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::File, FileDeliveryMethodMailer.delivery_method end def test_should_default_location_to_the_tmpdir @@ -71,3 +86,16 @@ class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end end +class CustomDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase + def setup + set_delivery_method :smtp + end + + def teardown + restore_delivery_method + end + + def test_should_be_the_set_delivery_method + assert_instance_of CustomDeliveryMethod, CustomerDeliveryMailer.delivery_method + end +end diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index e94aeff074..f8b002e0a7 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -20,28 +20,29 @@ class HelperMailer < ActionMailer::Base recipients recipient subject "using helpers" from "tester@example.com" - self.body = { :text => "emphasize me!" } + + @text = "emphasize me!" end def use_mail_helper(recipient) recipients recipient subject "using mailing helpers" from "tester@example.com" - self.body = { :text => - "But soft! What light through yonder window breaks? It is the east, " + - "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + - "which is sick and pale with grief that thou, her maid, art far more " + - "fair than she. Be not her maid, for she is envious! Her vestal " + - "livery is but sick and green, and none but fools do wear it. Cast " + - "it off!" - } + + @text = "But soft! What light through yonder window breaks? It is the east, " + + "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + + "which is sick and pale with grief that thou, her maid, art far more " + + "fair than she. Be not her maid, for she is envious! Her vestal " + + "livery is but sick and green, and none but fools do wear it. Cast " + + "it off!" end def use_helper_method(recipient) recipients recipient subject "using helpers" from "tester@example.com" - self.body = { :text => "emphasize me!" } + + @text = "emphasize me!" end private diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 50901f52ec..f37c26ff69 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -11,14 +11,18 @@ class AutoLayoutMailer < ActionMailer::Base recipients recipient subject "You have a mail" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>", :layout => 'spam') end def nolayout(recipient) recipients recipient subject "You have a mail" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>", :layout => false) end def multipart(recipient, type = nil) diff --git a/actionmailer/test/mail_render_test.rb b/actionmailer/test/mail_render_test.rb index 45811612eb..514f7ed798 100644 --- a/actionmailer/test/mail_render_test.rb +++ b/actionmailer/test/mail_render_test.rb @@ -5,14 +5,27 @@ class RenderMailer < ActionMailer::Base recipients recipient subject "using helpers" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" }) + + @world = "Earth" + render :inline => "Hello, <%= @world %>" end def file_template(recipient) recipients recipient subject "using helpers" from "tester@example.com" - body render(:file => "signed_up", :body => { :recipient => recipient }) + + @recipient = recipient + render :file => "templates/signed_up" + end + + def implicit_body(recipient) + recipients recipient + subject "using helpers" + from "tester@example.com" + + @recipient = recipient + render :template => "templates/signed_up" end def rxml_template(recipient) @@ -31,7 +44,9 @@ class RenderMailer < ActionMailer::Base recipients recipient subject "Including another template in the one being rendered" from "tester@example.com" - body render(:inline => "Hello, <%= render \"subtemplate\" %>", :body => { :world => "Earth" }) + + @world = "Earth" + render :inline => "Hello, <%= render \"subtemplate\" %>" end def initialize_defaults(method_name) @@ -69,6 +84,11 @@ class RenderHelperTest < Test::Unit::TestCase restore_delivery_method end + def test_implicit_body + mail = RenderMailer.create_implicit_body(@recipient) + assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip + end + def test_inline_template mail = RenderMailer.create_inline_template(@recipient) assert_equal "Hello, Earth", mail.body.strip diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 5584afa8be..f9365ea90c 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -15,18 +15,20 @@ end class TestMailer < ActionMailer::Base def signed_up(recipient) - @recipients = recipient - @subject = "[Signed up] Welcome #{recipient}" - @from = "system@loudthinking.com" - @body["recipient"] = recipient + recipients recipient + subject "[Signed up] Welcome #{recipient}" + from "system@loudthinking.com" + + @recipient = recipient end def cancelled_account(recipient) - self.recipients = recipient - self.subject = "[Cancelled] Goodbye #{recipient}" - self.from = "system@loudthinking.com" - self.sent_on = Time.local(2004, 12, 12) - self.body = "Goodbye, Mr. #{recipient}" + recipients recipient + subject "[Cancelled] Goodbye #{recipient}" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + + render :text => "Goodbye, Mr. #{recipient}" end def cc_bcc(recipient) @@ -36,7 +38,8 @@ class TestMailer < ActionMailer::Base sent_on Time.local(2004, 12, 12) cc "nobody@loudthinking.com" bcc "root@loudthinking.com" - body "Nothing to see here." + + render :text => "Nothing to see here." end def different_reply_to(recipient) @@ -45,50 +48,55 @@ class TestMailer < ActionMailer::Base from "system@loudthinking.com" sent_on Time.local(2008, 5, 23) reply_to "atraver@gmail.com" - body "Nothing to see here." + + render :text => "Nothing to see here." end def iso_charset(recipient) - @recipients = recipient - @subject = "testing isø charsets" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." - @charset = "iso-8859-1" + recipients recipient + subject "testing isø charsets" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + cc "nobody@loudthinking.com" + bcc "root@loudthinking.com" + charset "iso-8859-1" + + render :text => "Nothing to see here." end def unencoded_subject(recipient) - @recipients = recipient - @subject = "testing unencoded subject" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." + recipients recipient + subject "testing unencoded subject" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + cc "nobody@loudthinking.com" + bcc "root@loudthinking.com" + + render :text => "Nothing to see here." end def extended_headers(recipient) - @recipients = recipient - @subject = "testing extended headers" - @from = "Grytøyr <stian1@example.net>" - @sent_on = Time.local 2004, 12, 12 - @cc = "Grytøyr <stian2@example.net>" - @bcc = "Grytøyr <stian3@example.net>" - @body = "Nothing to see here." - @charset = "iso-8859-1" + recipients recipient + subject "testing extended headers" + from "Grytøyr <stian1@example.net>" + sent_on Time.local(2004, 12, 12) + cc "Grytøyr <stian2@example.net>" + bcc "Grytøyr <stian3@example.net>" + charset "iso-8859-1" + + render :text => "Nothing to see here." end def utf8_body(recipient) - @recipients = recipient - @subject = "testing utf-8 body" - @from = "Foo áëô îü <extended@example.net>" - @sent_on = Time.local 2004, 12, 12 - @cc = "Foo áëô îü <extended@example.net>" - @bcc = "Foo áëô îü <extended@example.net>" - @body = "åœö blah" - @charset = "utf-8" + recipients recipient + subject "testing utf-8 body" + from "Foo áëô îü <extended@example.net>" + sent_on Time.local(2004, 12, 12) + cc "Foo áëô îü <extended@example.net>" + bcc "Foo áëô îü <extended@example.net>" + charset "utf-8" + + render :text => "åœö blah" end def multipart_with_mime_version(recipient) @@ -128,7 +136,6 @@ class TestMailer < ActionMailer::Base subject "multipart example" from "test@example.com" sent_on Time.local(2004, 12, 12) - body "plain text default" content_type ct if ct part "text/html" do |p| @@ -138,15 +145,18 @@ class TestMailer < ActionMailer::Base attachment :content_type => "image/jpeg", :filename => "foo.jpg", :body => "123456789" + + render :text => "plain text default" end def implicitly_multipart_example(recipient, cs = nil, order = nil) - @recipients = recipient - @subject = "multipart example" - @from = "test@example.com" - @sent_on = Time.local 2004, 12, 12 - @body = { "recipient" => recipient } - @charset = cs if cs + recipients recipient + subject "multipart example" + from "test@example.com" + sent_on Time.local(2004, 12, 12) + + @charset = cs if cs + @recipient = recipient @implicit_parts_order = order if order end @@ -155,20 +165,22 @@ class TestMailer < ActionMailer::Base subject "Foo áëô îü" from "some.one@somewhere.test" template "implicitly_multipart_example" - body ({ "recipient" => "no.one@nowhere.test" }) + + @recipient = "no.one@nowhere.test" end def html_mail(recipient) recipients recipient subject "html mail" from "test@example.com" - body "<em>Emphasize</em> <strong>this</strong>" content_type "text/html" + + render :text => "<em>Emphasize</em> <strong>this</strong>" end def html_mail_with_underscores(recipient) subject "html mail with underscores" - body %{<a href="http://google.com" target="_blank">_Google</a>} + render :text => %{<a href="http://google.com" target="_blank">_Google</a>} end def custom_template(recipient) @@ -178,7 +190,7 @@ class TestMailer < ActionMailer::Base sent_on Time.local(2004, 12, 12) template "signed_up" - body["recipient"] = recipient + @recipient = recipient end def custom_templating_extension(recipient) @@ -187,15 +199,16 @@ class TestMailer < ActionMailer::Base from "system@loudthinking.com" sent_on Time.local(2004, 12, 12) - body["recipient"] = recipient + @recipient = recipient end def various_newlines(recipient) recipients recipient subject "various newlines" from "test@example.com" - body "line #1\nline #2\rline #3\r\nline #4\r\r" + - "line #5\n\nline#6\r\n\r\nline #7" + + render :text => "line #1\nline #2\rline #3\r\nline #4\r\r" + + "line #5\n\nline#6\r\n\r\nline #7" end def various_newlines_multipart(recipient) @@ -203,6 +216,7 @@ class TestMailer < ActionMailer::Base subject "various newlines multipart" from "test@example.com" content_type "multipart/alternative" + part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r" part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r" end @@ -212,10 +226,12 @@ class TestMailer < ActionMailer::Base subject "nested multipart" from "test@example.com" content_type "multipart/mixed" + part :content_type => "multipart/alternative", :content_disposition => "inline", :headers => { "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 + attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz" end @@ -224,6 +240,7 @@ class TestMailer < ActionMailer::Base subject "nested multipart with body" from "test@example.com" content_type "multipart/mixed" + part :content_type => "multipart/alternative", :content_disposition => "inline", :body => "Nothing to see here." do |p| p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>" end @@ -253,7 +270,7 @@ class TestMailer < ActionMailer::Base from "One: Two <test@example.com>" cc "Three: Four <test@example.com>" bcc "Five: Six <test@example.com>" - body "testing" + render :text => "testing" end def custom_content_type_attributes @@ -261,15 +278,15 @@ class TestMailer < ActionMailer::Base subject "custom content types" from "some.one@somewhere.test" content_type "text/plain; format=flowed" - body "testing" + render :text => "testing" end def return_path recipients "no.one@nowhere.test" subject "return path test" from "some.one@somewhere.test" - body "testing" headers "return-path" => "another@somewhere.test" + render :text => "testing" end def body_ivar(recipient) @@ -279,7 +296,13 @@ class TestMailer < ActionMailer::Base body :body => "foo", :bar => "baz" end - class <<self + def subject_with_i18n(recipient) + recipients recipient + from "system@loudthinking.com" + render :text => "testing" + end + + class << self attr_accessor :received_body end @@ -375,6 +398,15 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded end + def test_subject_with_i18n + assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } + assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject + + I18n.backend.store_translations('en', :actionmailer => {:subjects => {:test_mailer => {:subject_with_i18n => "New Subject!"}}}) + assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } + assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject + end + def test_custom_template expected = new_mail expected.to = @recipient @@ -554,7 +586,7 @@ class ActionMailerTest < Test::Unit::TestCase def test_doesnt_raise_errors_when_raise_delivery_errors_is_false ActionMailer::Base.raise_delivery_errors = false - TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception) + TestMailer.delivery_method.expects(:perform_delivery).raises(Exception) assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) } end @@ -1024,7 +1056,7 @@ end class MethodNamingTest < Test::Unit::TestCase class TestMailer < ActionMailer::Base def send - body 'foo' + render :text => 'foo' end end diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 65b07a71b8..34c5243936 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -4,13 +4,15 @@ class TestHelperMailer < ActionMailer::Base def test recipients "test@example.com" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>") end end class TestHelperMailerTest < ActionMailer::TestCase def test_setup_sets_right_action_mailer_options - assert_equal :test, ActionMailer::Base.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Test, ActionMailer::Base.delivery_method assert ActionMailer::Base.perform_deliveries assert_equal [], ActionMailer::Base.deliveries end diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 71286bd1cf..2224f6321c 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -1,9 +1,9 @@ require 'abstract_unit' class TestMailer < ActionMailer::Base - + default_url_options[:host] = 'www.basecamphq.com' - + def signed_up_with_url(recipient) @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @@ -52,25 +52,27 @@ class ActionMailerUrlTest < Test::Unit::TestCase end def test_signed_up_with_url - ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' - map.welcome 'welcome', :controller=>"foo", :action=>"bar" - end + 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 + 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 + end end end diff --git a/actionpack/Gemfile b/actionpack/Gemfile deleted file mode 100644 index 506458365f..0000000000 --- a/actionpack/Gemfile +++ /dev/null @@ -1,18 +0,0 @@ -rails_root = Pathname.new(File.dirname(__FILE__)).join("..") - -Gem.sources.each { |uri| source uri } - -gem "rack", "1.0.1", :git => "git://github.com/rack/rack.git", :branch => "rack-1.0" -gem "rack-test", "~> 0.5.0" -gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport") -gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel") -gem "activerecord", "3.0.pre", :vendored_at => rails_root.join("activerecord") -gem "erubis", "~> 2.6.0" - -only :test do - gem "mocha" - gem "sqlite3-ruby" - gem "RedCloth" -end - -disable_system_gems diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 157e3efd3c..99bdcc95fa 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -56,7 +56,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - if ENV['DOC_FILES'] + if ENV['DOC_FILES'] rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/)) else rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') @@ -70,8 +70,6 @@ spec = eval(File.read('actionpack.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do @@ -88,10 +86,10 @@ task :lines do codelines += 1 end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - + total_lines += lines total_codelines += codelines - + lines, codelines = 0, 0 end @@ -112,14 +110,14 @@ task :update_js => [ :update_scriptaculous ] # Publishing ------------------------------------------------------ desc "Publish the API documentation" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload end diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 76c5845f5b..1a6c4278c9 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -6,6 +6,7 @@ module AbstractController 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 diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index f3072fad74..d3b492ad09 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,3 +1,5 @@ +require 'active_support/dependencies' + module AbstractController module Helpers extend ActiveSupport::Concern @@ -57,23 +59,48 @@ module AbstractController end end - # Make a number of helper modules part of this class' default - # helpers. + # The +helper+ class method can take a series of helper module names, a block, or both. # # ==== Parameters - # *args<Array[Module]>:: Modules to be included - # block<Block>:: Evalulate the block in the context - # of the helper module. Any methods defined in the block - # will be helpers. + # *args<Array[Module, Symbol, String, :all]> + # block<Block>:: A block defining helper methods + # + # ==== Examples + # When the argument is a module it will be included directly in the template class. + # helper FooHelper # => includes FooHelper + # + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers + # when working with namespaced controllers, or other cases where the file containing the helper definition is not + # in one of Rails' standard load paths: + # helper :foo # => requires 'foo_helper' and includes FooHelper + # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper + # + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # to the template. + # + # # One line + # helper { def hello() "Hello, world!" end } + # + # # Multi-line + # helper do + # def foo(bar) + # "#{bar} is the very best" + # end + # end + # + # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of + # +symbols+, +strings+, +modules+ and blocks. + # + # helper(:three, BlindHelper) { def mice() 'mice' end } + # def helper(*args, &block) self._helper_serial = AbstractController::Helpers.next_serial + 1 - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - end + _modules_for_helpers(args).each do |mod| + add_template_helper(mod) end + _helpers.module_eval(&block) if block_given? end @@ -87,6 +114,38 @@ module AbstractController def add_template_helper(mod) _helpers.module_eval { include mod } end + + # Returns a list of modules, normalized from the acceptable kinds of + # helpers with the following behavior: + # + # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", + # and "foo_bar_helper.rb" is loaded using require_dependency. + # + # Module:: No further processing + # + # After loading the appropriate files, the corresponding modules + # are returned. + # + # ==== Parameters + # args<Array[String, Symbol, Module]>:: A list of helpers + # + # ==== Returns + # Array[Module]:: A normalized list of modules for the list of + # helpers provided. + def _modules_for_helpers(args) + args.flatten.map! do |arg| + case arg + when String, Symbol + file_name = "#{arg.to_s.underscore}_helper" + require_dependency(file_name, "Missing helper file helpers/%s.rb") + file_name.camelize.constantize + when Module + arg + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end + end + end end end end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 796ef40584..8293e79b0a 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -157,6 +157,7 @@ module AbstractController end private + # This will be overwritten by _write_layout_method def _layout(details) end @@ -171,6 +172,34 @@ module AbstractController name && _find_layout(name, details) end + # Determine the layout for a given name and details, taking into account + # the name type. + # + # ==== Parameters + # name<String|TrueClass|FalseClass|Symbol>:: The name of the template + # details<Hash{Symbol => Object}>:: A list of details to restrict + # the lookup to. By default, layout lookup is limited to the + # formats specified for the current request. + def _layout_for_option(name, details) + case name + when String then _layout_for_name(name, details) + when true then _default_layout(details, true) + when :default then _default_layout(details, false) + when false, nil then nil + else + raise ArgumentError, + "String, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + + def _determine_template(options) + super + + return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) + layout = options.key?(:layout) ? options[:layout] : :default + options[:_layout] = _layout_for_option(layout, options[:_template].details) + end + # Take in the name and details and find a Template. # # ==== Parameters diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb new file mode 100644 index 0000000000..ee7b43cb9f --- /dev/null +++ b/actionpack/lib/abstract_controller/localized_cache.rb @@ -0,0 +1,49 @@ +module AbstractController + class HashKey + @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } + + def self.get(klass, formats, locale) + @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) + end + + attr_accessor :hash + def initialize(klass, formats, locale) + @formats, @locale = formats, locale + @hash = [formats, locale].hash + end + + alias_method :eql?, :equal? + + def inspect + "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>" + end + end + + module LocalizedCache + extend ActiveSupport::Concern + + module ClassMethods + def clear_template_caches! + ActionView::Partials::PartialRenderer::TEMPLATES.clear + template_cache.clear + super + end + + def template_cache + @template_cache ||= Hash.new {|h,k| h[k] = {} } + end + end + + def render(options) + Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) + super + end + + private + + def with_template_cache(name) + self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super + end + + end +end diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index f4d017b8e5..27ba5be45f 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/logger' +require 'active_support/benchmarkable' module AbstractController module Logger @@ -6,22 +7,7 @@ module AbstractController included do cattr_accessor :logger - end - - module ClassMethods #:nodoc: - # Logs a message appending the value measured. - def log_with_time(message, time, log_level=::Logger::DEBUG) - return unless logger && logger.level >= log_level - logger.add(log_level, "#{message} (%.1fms)" % time) - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, ::Logger::ERROR if logge - yield - ensure - logger.level = old_logger_level if logger - end + extend ActiveSupport::Benchmarkable end # A class that allows you to defer expensive processing @@ -47,7 +33,7 @@ module AbstractController # Override process_action in the AbstractController::Base # to log details about the method. def process_action(action) - event = ActiveSupport::Orchestra.instrument(:process_action, + result = ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do super end @@ -56,13 +42,13 @@ module AbstractController log = DelayedLog.new do "\n\nProcessing #{self.class.name}\##{action_name} " \ "to #{request.formats} (for #{request_origin}) " \ - "(%.1fms) [#{request.method.to_s.upcase}]" % event.duration + "[#{request.method.to_s.upcase}]" end logger.info(log) end - event.result + result end private diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb index bbf941aa32..0aae2b18e9 100644 --- a/actionpack/lib/abstract_controller/rendering_controller.rb +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -8,9 +8,7 @@ module AbstractController included do attr_internal :formats - extlib_inheritable_accessor :_view_paths - self._view_paths ||= ActionView::PathSet.new end @@ -99,6 +97,7 @@ module AbstractController end private + # Take in a set of options and determine the template to render # # ==== Options @@ -109,6 +108,18 @@ module AbstractController # to a directory. # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.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, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + end + name = (options[:_template_name] || action_name).to_s options[:_template] ||= with_template_cache(name) do @@ -128,6 +139,10 @@ module AbstractController yield end + def format_for_text + Mime[:text] + end + module ClassMethods def clear_template_caches! end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 6702cb47f8..03a40e4fce 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -2,6 +2,8 @@ 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" @@ -18,22 +20,20 @@ module ActionController autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" - # Ported modules - # require 'action_controller/routing' 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/routing/generation/polymorphic_routes' + autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Resources, 'action_controller/routing/resources' + 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/routing/generation/url_rewriter' - autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' + autoload :UrlRewriter, 'action_controller/url_rewriter' + autoload :UrlWriter, 'action_controller/url_rewriter' autoload :Verification, 'action_controller/metal/verification' autoload :Flash, 'action_controller/metal/flash' @@ -54,8 +54,6 @@ module ActionController autoload :RenderError, 'action_controller/metal/exceptions' autoload :SessionOverflowError, 'action_controller/metal/exceptions' autoload :UnknownHttpMethod, 'action_controller/metal/exceptions' - - autoload :Routing, 'action_controller/routing' end autoload :HTML, 'action_controller/vendor/html-scanner' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5338a70104..4c026fe5f7 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -15,6 +15,7 @@ module ActionController include ActionController::ConditionalGet include ActionController::RackConvenience include ActionController::Benchmarking + include ActionController::Configuration # Legacy modules include SessionManagement diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 38cf1da6a8..083d6328af 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -3,26 +3,30 @@ require 'uri' require 'set' module ActionController #:nodoc: - # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls - # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. + # Caching is a cheap way of speeding up slow applications by keeping the result of + # calculations, renderings, and database calls around for subsequent requests. + # Action Controller affords you three approaches in varying levels of granularity: + # Page, Action, Fragment. # - # You can read more about each approach and the sweeping assistance by clicking the modules below. - # - # Note: To turn off all caching and sweeping, set Base.perform_caching = false. + # You can read more about each approach and the sweeping assistance by clicking the + # modules below. # + # Note: To turn off all caching and sweeping, set + # config.action_controller.perform_caching = false. # # == Caching stores # - # All the caching stores from ActiveSupport::Cache are available to be used as backends for Action Controller caching. This setting only - # affects action and fragment caching as page caching is always written to disk. + # All the caching stores from ActiveSupport::Cache are available to be used as backends + # for Action Controller caching. This setting only affects action and fragment caching + # as page caching is always written to disk. # # Configuration examples (MemoryStore is the default): # - # ActionController::Base.cache_store = :memory_store - # ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" - # ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" - # ActionController::Base.cache_store = :mem_cache_store, "localhost" - # ActionController::Base.cache_store = MyOwnStore.new("parameter") + # config.action_controller.cache_store = :memory_store + # config.action_controller.cache_store = :file_store, "/path/to/cache/directory" + # config.action_controller.cache_store = :drb_store, "druby://localhost:9192" + # config.action_controller.cache_store = :mem_cache_store, "localhost" + # config.action_controller.cache_store = MyOwnStore.new("parameter") module Caching extend ActiveSupport::Concern @@ -46,25 +50,31 @@ module ActionController #:nodoc: @@perform_caching = true cattr_accessor :perform_caching + end - def self.cache_configured? + module ClassMethods + def cache_configured? perform_caching && cache_store end end - protected - # Convenience accessor - def cache(key, options = {}, &block) - if cache_configured? - cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) - else - yield - end - end + def caching_allowed? + request.get? && response.status == 200 + end - private - def cache_configured? - self.class.cache_configured? + protected + # Convenience accessor + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield end + end + + private + def cache_configured? + self.class.cache_configured? + end end end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index cb0c3a1384..35111a4b92 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -2,9 +2,12 @@ require 'set' module ActionController #:nodoc: module Caching - # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, - # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which - # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: + # Action caching is similar to page caching by the fact that the entire + # output of the response is cached, but unlike page caching, every + # request still goes through the Action Pack. The key benefit + # of this is that filters are run before the cache is served, which + # allows for authentication and other restrictions on whether someone + # is allowed to see the cache. Example: # # class ListsController < ApplicationController # before_filter :authenticate, :except => :public @@ -12,45 +15,64 @@ module ActionController #:nodoc: # caches_action :index, :show, :feed # end # - # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the - # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. + # In this example, the public action doesn't require authentication, + # so it's possible to use the faster page caching method. But both + # the show and feed action are to be shielded behind the authenticate + # filter, so we need to implement those as action caches. # - # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both - # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named - # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and - # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. + # Action caching internally uses the fragment caching and an around + # filter to do the job. The fragment cache is named according to both + # the current host and the path. So a page that is accessed at + # http://david.somewhere.com/lists/show/1 will result in a fragment named + # "david.somewhere.com/lists/show/1". This allows the cacher to + # differentiate between "david.somewhere.com/lists/" and + # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting + # the subdomain-as-account-key pattern. # - # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt> - # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same - # as <tt>:action => 'list', :format => :xml</tt>. + # Different representations of the same resource, e.g. + # <tt>http://david.somewhere.com/lists</tt> and + # <tt>http://david.somewhere.com/lists.xml</tt> + # are treated like separate requests and so are cached separately. + # Keep in mind when expiring an action cache that + # <tt>:action => 'lists'</tt> is not the same as + # <tt>:action => 'list', :format => :xml</tt>. # - # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy - # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. + # You can set modify the default action cache path by passing a + # :cache_path option. This will be passed directly to + # ActionCachePath.path_for. This is handy for actions with multiple + # possible routes that should be cached differently. If a block is + # given, it is called with the current controller instance. # - # And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached. + # And you can also use :if (or :unless) to pass a Proc that + # specifies when the action should be cached. # # Finally, if you are using memcached, you can also pass :expires_in. # # class ListsController < ApplicationController # before_filter :authenticate, :except => :public # caches_page :public - # caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request - # caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour - # caches_action :feed, :cache_path => Proc.new { |controller| - # controller.params[:user_id] ? - # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) : - # controller.send(:list_url, controller.params[:id]) } + # caches_action :index, :if => proc do |c| + # !c.request.format.json? # cache if is not a JSON request + # end + # + # caches_action :show, :cache_path => { :project => 1 }, + # :expires_in => 1.hour + # + # caches_action :feed, :cache_path => proc do |controller| + # if controller.params[:user_id] + # controller.send(:user_list_url, + # controller.params[:user_id], controller.params[:id]) + # else + # controller.send(:list_url, controller.params[:id]) + # end + # end # end # - # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information. + # If you pass :layout => false, it will only cache your action + # content. It is useful when your layout has dynamic information. # module Actions - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - attr_accessor :rendered_action_cache, :action_cache_path - end - end + extend ActiveSupport::Concern module ClassMethods # Declares that +actions+ should be cached. @@ -58,120 +80,83 @@ module ActionController #:nodoc: def caches_action(*actions) return unless cache_configured? options = actions.extract_options! - filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) } - - cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options) + filter_options = options.extract!(:if, :unless).merge(:only => actions) + cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options) - around_filter cache_filter, filter_options + around_filter ActionCacheFilter.new(cache_options), filter_options end end - protected - def expire_action(options = {}) - return unless cache_configured? + def _render_cache_fragment(cache, extension, layout) + render :text => cache, :layout => layout, :content_type => Mime[extension || :html] + end - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false)) - end - else - expire_fragment(ActionCachePath.path_for(self, options, false)) - end + def _save_fragment(name, layout, options) + return unless caching_allowed? + + content = layout ? view_context.content_for(:layout) : response_body + write_fragment(name, content, options) + end + + protected + def expire_action(options = {}) + return unless cache_configured? + + actions = options[:action] + if actions.is_a?(Array) + actions.each {|action| expire_action(options.merge(:action => action)) } + else + expire_fragment(ActionCachePath.new(self, options, false).path) end + end class ActionCacheFilter #:nodoc: def initialize(options, &block) - @options = options + @cache_path, @store_options, @layout = + options.values_at(:cache_path, :store_options, :layout) end def filter(controller) - should_continue = before(controller) - yield if should_continue - after(controller) - end - - def before(controller) - cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path))) - - if cache = controller.read_fragment(cache_path.path, @options[:store_options]) - controller.rendered_action_cache = true - set_content_type!(controller, cache_path.extension) - options = { :text => cache } - options.merge!(:layout => true) if cache_layout? - controller.__send__(:render, options) - false + path_options = if @cache_path.respond_to?(:call) + controller.instance_exec(controller, &@cache_path) else - controller.action_cache_path = cache_path - end - end - - def after(controller) - return if controller.rendered_action_cache || !caching_allowed(controller) - action_content = cache_layout? ? content_for_layout(controller) : controller.response.body - controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options]) - end - - private - def set_content_type!(controller, extension) - controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension + @cache_path end - def path_options_for(controller, options) - ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {} - end - - def caching_allowed(controller) - controller.request.get? && controller.response.status.to_i == 200 - end - - def cache_layout? - @options[:layout] == false - end + cache_path = ActionCachePath.new(controller, path_options || {}) - def content_for_layout(controller) - template = controller.view_context - template.layout && template.instance_variable_get('@cached_content_for_layout') + if cache = controller.read_fragment(cache_path.path, @store_options) + controller._render_cache_fragment(cache, cache_path.extension, @layout == false) + else + yield + controller._save_fragment(cache_path.path, @layout == false, @store_options) end + end end class ActionCachePath attr_reader :path, :extension - class << self - def path_for(controller, options, infer_extension = true) - new(controller, options, infer_extension).path - end - end - - # If +infer_extension+ is true, the cache path extension is looked up from the request's path & format. - # This is desirable when reading and writing the cache, but not when expiring the cache - - # expire_action should expire the same files regardless of the request format. + # If +infer_extension+ is true, the cache path extension is looked up from the request's + # path & format. This is desirable when reading and writing the cache, but not when + # expiring the cache - expire_action should expire the same files regardless of the + # request format. def initialize(controller, options = {}, infer_extension = true) if infer_extension - extract_extension(controller.request) - options = options.reverse_merge(:format => @extension) if options.is_a?(Hash) + @extension = controller.params[:format] + options.reverse_merge!(:format => @extension) if options.is_a?(Hash) end - path = controller.url_for(options).split('://').last - normalize!(path) - add_extension!(path, @extension) - @path = URI.unescape(path) + path = controller.url_for(options).split(%r{://}).last + @path = normalize!(path) end - private - def normalize!(path) - path << 'index' if path[-1] == ?/ - end - - def add_extension!(path, extension) - path << ".#{extension}" if extension and !path.ends_with?(extension) - end - - def extract_extension(request) - # Don't want just what comes after the last '.' to accommodate multi part extensions - # such as tar.gz. - @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format - end + private + def normalize!(path) + path << 'index' if path[-1] == ?/ + path << ".#{extension}" if extension and !path.ends_with?(extension) + URI.unescape(path) + end end end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 59e24619e3..8c1167d526 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -51,40 +51,32 @@ module ActionController #:nodoc: # Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) def write_fragment(key, content, options = nil) return content unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do + + ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do cache_store.write(key, content, options) end - - self.class.log_with_time("Cached fragment miss: #{key}", event.duration) content end # Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) def read_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do + + ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do cache_store.read(key, options) end - - self.class.log_with_time("Cached fragment hit: #{key}", event.duration) - event.result end # Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats) def fragment_exist?(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do + + ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do cache_store.exist?(key, options) end - - self.class.log_with_time("Cached fragment exists?: #{key}", event.duration) - event.result end # Removes fragments from the cache. @@ -106,11 +98,10 @@ module ActionController #:nodoc: # method (or <tt>delete_matched</tt>, for Regexp keys.) def expire_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) unless key.is_a?(Regexp) message = nil - event = ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do + ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do if key.is_a?(Regexp) message = "Expired fragments matching: #{key.source}" cache_store.delete_matched(key, options) @@ -119,9 +110,6 @@ module ActionController #:nodoc: cache_store.delete(key, options) end end - - self.class.log_with_time(message, event.duration) - event.result end end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 4fb154470f..d46f528c7e 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -64,12 +64,9 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - event = ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do + ActiveSupport::Notifications.instrument(:expire_page, :path => path) do File.delete(path) if File.exist?(path) end - - log_with_time("Expired page: #{path}", event.duration) - event.result end # Manually cache the +content+ in the key determined by +path+. Example: @@ -78,13 +75,10 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - event = ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do + ActiveSupport::Notifications.instrument(:cache_page, :path => path) do FileUtils.makedirs(File.dirname(path)) File.open(path, "wb+") { |f| f.write(content) } end - - log_with_time("Cached page: #{path}", event.duration) - event.result end # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index c1be264ffb..871f41bfdd 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -70,7 +70,7 @@ module ActionController #:nodoc: protected # gets the action cache path for the given options. def action_path_for(options) - ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) + Actions::ActionCachePath.new(controller, options).path end # Retrieve instance variables set in the controller. diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb index d98e9ac7bd..589061e77c 100644 --- a/actionpack/lib/action_controller/deprecated.rb +++ b/actionpack/lib/action_controller/deprecated.rb @@ -1,2 +1,4 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response +ActionController::Routing = ActionDispatch::Routing +ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb index 008fb54715..e04da42637 100644 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb @@ -50,7 +50,7 @@ module ActionController def new # DEPRECATE Rails application fallback - Rails.application.new + Rails.application end end end diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb deleted file mode 100644 index 4e3b67de20..0000000000 --- a/actionpack/lib/action_controller/legacy/layout.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/class' -require 'active_support/core_ext/class/delegating_attributes' -require 'active_support/core_ext/class/inheritable_attributes' - -module ActionController #:nodoc: - # MegasuperultraHAX - # plz refactor ActionMailer - class Base - @@exempt_from_layout = [ActionView::TemplateHandlers::RJS] - cattr_accessor :exempt_from_layout - end - - module Layout #:nodoc: - def self.included(base) - base.extend(ClassMethods) - base.class_inheritable_accessor :layout_name, :layout_conditions - end - - # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in - # repeated setups. The inclusion pattern has pages that look like this: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # // The header part of this layout - # <%= yield %> - # // The footer part of this layout - # - # And then you have content pages that look like this: - # - # hello world - # - # At rendering time, the content page is computed and then inserted in the layout, like this: - # - # // The header part of this layout - # hello world - # // The footer part of this layout - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - # <h1><%= @page_title %></h1> - # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - # <h1>Welcome</h1> - # Off-world colonies offers you a chance to start a new life - # - # == Automatic layout assignment - # - # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically - # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named - # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as - # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt> - # and this will be set as the default controller if there is no layout with the same name as the current controller and there is - # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout. - # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>. - # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set. - # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child - # class has a layout with the same name. - # - # == Inheritance for layouts - # - # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples: - # - # class BankController < ActionController::Base - # layout "bank_standard" - # - # class InformationController < BankController - # - # class VaultController < BankController - # layout :access_level_layout - # - # class EmployeeController < BankController - # layout nil - # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>. - # Otherwise, it will be looked up relative to the template root. - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. - # - # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so - # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. - # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # def help - # render :action => "help", :layout => "help" - # end - # end - # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. - module ClassMethods - extend ActiveSupport::Memoizable - - # If a layout is specified, all rendered actions will have their result rendered - # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action - # performance and have access to them as any normal template would. - def layout(template_name, conditions = {}, auto = false) - add_layout_conditions(conditions) - self.layout_name = template_name - end - - def memoized_default_layout(formats) #:nodoc: - self.layout_name || begin - layout = default_layout_name - layout.is_a?(String) ? find_layout(layout, formats) : layout - rescue ActionView::MissingTemplate - end - end - - def default_layout(*args) - memoized_default_layout(*args) - @_memoized_default_layout ||= {} - @_memoized_default_layout[args] ||= memoized_default_layout(*args) - end - - def memoized_find_layout(layout, formats) #:nodoc: - return layout if layout.nil? || layout.respond_to?(:render) - prefix = layout.to_s =~ /layouts\// ? nil : "layouts" - find_template(layout.to_s, {:formats => formats}, :_prefix => prefix) - end - - def find_layout(*args) - @_memoized_find_layout ||= {} - @_memoized_find_layout[args] ||= memoized_find_layout(*args) - end - - def layout_list #:nodoc: - Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } - end - memoize :layout_list - - def default_layout_name - layout_match = name.underscore.sub(/_controller$/, '') - if layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? - superclass.default_layout_name if superclass.respond_to?(:default_layout_name) - else - layout_match - end - end - memoize :default_layout_name - - private - def add_layout_conditions(conditions) - # :except => :foo == :except => [:foo] == :except => "foo" == :except => ["foo"] - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } - write_inheritable_hash(:layout_conditions, conditions) - end - end - - def active_layout(name) - name = self.class.default_layout(formats) if name == true - - layout_name = case name - when Symbol then __send__(name) - when Proc then name.call(self) - else name - end - - self.class.find_layout(layout_name, formats) - end - - def _pick_layout(layout_name = nil, implicit = false) - return unless layout_name || implicit - layout_name = true if layout_name.nil? - active_layout(layout_name) if action_has_layout? && layout_name - end - - private - def action_has_layout? - if conditions = self.class.layout_conditions - if only = conditions[:only] - return only.include?(action_name) - elsif except = conditions[:except] - return !except.include?(action_name) - end - end - true - end - - end -end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index e9007d3631..6f89bf5d67 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -109,6 +109,10 @@ module ActionController middleware_stack end + def self.call(env) + action(env['action_dispatch.request.path_parameters'][:action]).call(env) + end + # Return a rack endpoint for the given action. Memoize the endpoint, so # multiple calls into MyController.action will return the same object # for the same action. diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb index d4cb1e122d..e58df69172 100644 --- a/actionpack/lib/action_controller/metal/benchmarking.rb +++ b/actionpack/lib/action_controller/metal/benchmarking.rb @@ -1,4 +1,4 @@ -require 'benchmark' +require 'active_support/core_ext/benchmark' module ActionController #:nodoc: # The benchmarking module times the performance of actions and reports to the logger. If the Active Record @@ -6,25 +6,6 @@ module ActionController #:nodoc: module Benchmarking #:nodoc: extend ActiveSupport::Concern - module ClassMethods - # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it - # (unless <tt>use_silence</tt> is set to false). - # - # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it - # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark - # will only be conducted if the log level is low enough. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level == log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") - result - else - yield - end - end - end - protected def render(*args, &block) if logger @@ -45,7 +26,7 @@ module ActionController #:nodoc: else super end - end + end private def process_action(*args) diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 22f9ab219c..c251d79f4e 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -25,9 +25,11 @@ module ActionController # cattr_reader :protected_instance_variables cattr_accessor :protected_instance_variables - self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params - @_flash @_response) + self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render + @variables_added @request_origin @url + @parent_controller @action_name + @before_filter_chain_aborted @_headers @_params + @_flash @_response) # Indicates whether or not optimise the generated named # route helper methods diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 8575d30335..5156fbc1d5 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -3,6 +3,7 @@ module ActionController extend ActiveSupport::Concern include RackConvenience + include Head # Sets the etag, last_modified, or both on the response and renders a # "304 Not Modified" response if the request is already fresh. @@ -27,43 +28,9 @@ module ActionController response.etag = options[:etag] if options[:etag] response.last_modified = options[:last_modified] if options[:last_modified] + response.cache_control[:public] = true if options[:public] - if options[:public] - response.cache_control[:public] = true - end - - if request.fresh?(response) - head :not_modified - end - end - - # 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 - # significant headers: - # - # head :created, :location => person_path(@person) - # - # It can also be used to return exceptional conditions: - # - # return head(:method_not_allowed) unless request.post? - # return head(:bad_request) unless valid_request? - # render - def head(*args) - if args.length > 2 - raise ArgumentError, "too many arguments to head" - elsif args.empty? - raise ArgumentError, "too few arguments to head" - end - options = args.extract_options! - status = args.shift || options.delete(:status) || :ok - location = options.delete(:location) - - options.each do |key, value| - headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s - end - - render :nothing => true, :status => status, :location => location + head :not_modified if request.fresh?(response) end # Sets the etag and/or last_modified on the response and checks it against @@ -113,7 +80,7 @@ module ActionController # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or # intermediate caches (like caching proxy servers). def expires_now #:doc: - response.headers["Cache-Control"] = "no-cache" + response.cache_control.replace(:no_cache => true) end end end diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb new file mode 100644 index 0000000000..5c829853b7 --- /dev/null +++ b/actionpack/lib/action_controller/metal/configuration.rb @@ -0,0 +1,28 @@ +module ActionController + module Configuration + extend ActiveSupport::Concern + + def config + @config ||= self.class.config + end + + def config=(config) + @config = config + end + + module ClassMethods + def default_config + @default_config ||= {} + end + + def config + self.config ||= default_config + end + + def config=(config) + @config = ActiveSupport::OrderedHash.new + @config.merge!(config) + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index c328db8beb..6855ca1478 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -44,24 +44,31 @@ module ActionController #:nodoc: # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. module Cookies - def self.included(base) - base.helper_method :cookies + extend ActiveSupport::Concern + + include RackConvenience + + included do + helper_method :cookies end - protected - # Returns the cookie container, which operates as described above. - def cookies - @cookies ||= CookieJar.new(self) - end + protected + # Returns the cookie container, which operates as described above. + def cookies + @cookies ||= CookieJar.build(request, response) + end end class CookieJar < Hash #:nodoc: - def initialize(controller) - @controller, @cookies = controller, controller.request.cookies - super() - update(@cookies) + def self.build(request, response) + new.tap do |hash| + hash.update(request.cookies) + hash.response = response + end end + attr_accessor :response + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) super(name.to_s) @@ -72,13 +79,16 @@ module ActionController #:nodoc: def []=(key, options) if options.is_a?(Hash) options.symbolize_keys! + value = options[:value] else - options = { :value => options } + value = options + options = { :value => value } end - options[:path] = "/" unless options.has_key?(:path) - super(key.to_s, options[:value]) - @controller.response.set_cookie(key, options) + 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 @@ -86,9 +96,9 @@ module ActionController #:nodoc: # an options hash to delete cookies with extra data such as a <tt>:path</tt>. def delete(key, options = {}) options.symbolize_keys! - options[:path] = "/" unless options.has_key?(:path) + options[:path] ||= "/" value = super(key.to_s) - @controller.response.delete_cookie(key, options) + response.delete_cookie(key, options) value end end diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb index 4259d9de19..a53c052075 100644 --- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb +++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb @@ -4,10 +4,6 @@ module ActionController include AbstractController::Logger - included do - include InstanceMethodsForNewBase - end - module ClassMethods # Replace sensitive parameter data from the request log. # Filters parameters that have any of the arguments as a substring. @@ -17,8 +13,6 @@ module ActionController # can be replaced using String#replace or similar method. # # Examples: - # filter_parameter_logging - # => Does nothing, just slows the logging process down # # filter_parameter_logging :password # => replaces the value to all keys matching /password/i with "[FILTERED]" @@ -33,64 +27,51 @@ module ActionController # => reverses the value to all keys matching /secret/i, and # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" def filter_parameter_logging(*filter_words, &block) - parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 + raise "You must filter at least one word from logging" if filter_words.empty? + + parameter_filter = Regexp.new(filter_words.join('|'), true) - define_method(:filter_parameters) do |unfiltered_parameters| - filtered_parameters = {} + define_method(:filter_parameters) do |original_params| + filtered_params = {} - unfiltered_parameters.each do |key, value| + original_params.each do |key, value| if key =~ parameter_filter - filtered_parameters[key] = '[FILTERED]' + value = '[FILTERED]' elsif value.is_a?(Hash) - filtered_parameters[key] = filter_parameters(value) + value = filter_parameters(value) elsif value.is_a?(Array) - filtered_parameters[key] = value.collect do |item| - filter_parameters(item) - end + value = value.map { |item| filter_parameters(item) } elsif block_given? key = key.dup value = value.dup if value.duplicable? yield key, value - filtered_parameters[key] = value - else - filtered_parameters[key] = value end + + filtered_params[key] = value end - filtered_parameters + filtered_params end protected :filter_parameters end end - module InstanceMethodsForNewBase - # TODO : Fix the order of information inside such that it's exactly same as the old base - def process(*) - ret = super - - if logger - parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup - parameters = parameters.except!(:controller, :action, :format, :_method, :only_path) + INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path] - unless parameters.empty? - # TODO : Move DelayedLog to AS - log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" } - logger.info(log) - end - end - - ret + def process(*) + response = super + if logger + parameters = filter_parameters(params).except!(*INTERNAL_PARAMS) + logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty? end + response end - private + protected - # TODO : This method is not needed for the new base - def log_processing_for_parameters - parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup - parameters = parameters.except!(:controller, :action, :format, :_method) - - logger.info " Parameters: #{parameters.inspect}" unless parameters.empty? + def filter_parameters(params) + params.dup end + end end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 590f9be3ac..f43900faa0 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -49,7 +49,7 @@ module ActionController #:nodoc: class FlashHash < Hash def initialize #:nodoc: super - @used = {} + @used = Set.new end def []=(k, v) #:nodoc: @@ -65,7 +65,7 @@ module ActionController #:nodoc: alias :merge! :update def replace(h) #:nodoc: - @used = {} + @used = Set.new super end @@ -104,8 +104,8 @@ module ActionController #:nodoc: # This method is called automatically by filters, so you generally don't need to care about it. def sweep #:nodoc: keys.each do |k| - unless @used[k] - use(k) + unless @used.include?(k) + @used << k else delete(k) @used.delete(k) @@ -113,47 +113,45 @@ module ActionController #:nodoc: end # clean up after keys that could have been left over by calling reject! or shift on the flash - (@used.keys - keys).each{ |k| @used.delete(k) } + (@used - keys).each{ |k| @used.delete(k) } end - def store(session, key = "flash") + def store(session) return if self.empty? - session[key] = self + session["flash"] = self end - private - # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods - # use() # marks the entire flash as used - # use('msg') # marks the "msg" entry as used - # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) - # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) - # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself - # if no key is passed. - def use(key = nil, used = true) - Array(key || keys).each { |k| @used[k] = used } - return key ? self[key] : self - end + private + # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods + # use() # marks the entire flash as used + # use('msg') # marks the "msg" entry as used + # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) + # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) + # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself + # if no key is passed. + def use(key = nil, used = true) + Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } + return key ? self[key] : self + end end protected def process_action(method_name) super - if defined? @_flash - @_flash.store(session) - remove_instance_variable(:@_flash) - end + @_flash.store(session) if @_flash + @_flash = nil end def reset_session super - remove_instance_variable(:@_flash) if defined?(@_flash) + @_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: - if !defined?(@_flash) + unless @_flash @_flash = session["flash"] || FlashHash.new @_flash.sweep end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb new file mode 100644 index 0000000000..68fa0a0402 --- /dev/null +++ b/actionpack/lib/action_controller/metal/head.rb @@ -0,0 +1,27 @@ +module ActionController + module Head + # 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 + # significant headers: + # + # head :created, :location => person_path(@person) + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + def head(status, options = {}) + options, status = status, nil if status.is_a?(Hash) + status ||= options.delete(:status) || :ok + location = options.delete(:location) + + options.each do |key, value| + headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s + end + + render :nothing => true, :status => status, :location => location + 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 7c52779064..b4325e24ad 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,5 +1,3 @@ -require 'active_support/dependencies' - module ActionController # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, # +numbers+ and model objects, to name a few. These helpers are available to all templates @@ -54,7 +52,7 @@ module ActionController included do # Set the default directory for helpers extlib_inheritable_accessor(:helpers_dir) do - defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers" + defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers" end end @@ -64,46 +62,6 @@ module ActionController super end - # The +helper+ class method can take a series of helper module names, a block, or both. - # - # ==== Parameters - # *args<Array[Module, Symbol, String, :all]> - # block<Block>:: A block defining helper methods - # - # ==== Examples - # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file - # and include the module in the template class. The second form illustrates how to include custom helpers - # when working with namespaced controllers, or other cases where the file containing the helper definition is not - # in one of Rails' standard load paths: - # helper :foo # => requires 'foo_helper' and includes FooHelper - # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper - # - # When the argument is a module it will be included directly in the template class. - # helper FooHelper # => includes FooHelper - # - # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers beneath - # <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT). - # helper :all - # - # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available - # to the template. - # # One line - # helper { def hello() "Hello, world!" end } - # # Multi-line - # helper do - # def foo(bar) - # "#{bar} is the very best" - # end - # end - # - # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of - # +symbols+, +strings+, +modules+ and blocks. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # - def helper(*args, &block) - super(*_modules_for_helpers(args), &block) - end - # Declares helper accessors for controller attributes. For example, the # following adds new +name+ and <tt>name=</tt> instance methods to a # controller and makes them available to the view: @@ -123,15 +81,8 @@ module ActionController end private - # Returns a list of modules, normalized from the acceptable kinds of - # helpers with the following behavior: - # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", - # and "foo_bar_helper.rb" is loaded using require_dependency. - # :all:: Loads all modules in the #helpers_dir - # Module:: No further processing - # - # After loading the appropriate files, the corresponding modules - # are returned. + # Overwrite _modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_dir. # # ==== Parameters # args<Array[String, Symbol, Module, all]>:: A list of helpers @@ -140,20 +91,8 @@ module ActionController # Array[Module]:: A normalized list of modules for the list of # helpers provided. def _modules_for_helpers(args) - args.flatten.map! do |arg| - case arg - when :all - _modules_for_helpers all_application_helpers - when String, Symbol - file_name = "#{arg.to_s.underscore}_helper" - require_dependency(file_name, "Missing helper file helpers/%s.rb") - file_name.camelize.constantize - when Module - arg - else - raise ArgumentError, "helper must be a String, Symbol, or Module" - end - end + args += all_application_helpers if args.delete(:all) + super(args) end def default_helper_module! diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index cac529b1ae..cc7088248a 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -167,26 +167,5 @@ module ActionController controller_path end end - - private - def _determine_template(options) - super - - return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - def _layout_for_option(name, details) - case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) - when false, nil then nil - else - raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 3026067868..468c5f4fae 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -3,7 +3,8 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - class_inheritable_reader :mimes_for_respond_to + extlib_inheritable_accessor :responder, :mimes_for_respond_to, :instance_writer => false + self.responder = ActionController::Responder clear_respond_to end @@ -46,7 +47,7 @@ module ActionController #:nodoc: # Clear all mimes in respond_to. # def clear_respond_to - write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new) + self.mimes_for_respond_to = ActiveSupport::OrderedHash.new end end @@ -221,10 +222,6 @@ module ActionController #:nodoc: end end - def responder - ActionController::Responder - end - protected # Collect mimes declared in the class method respond_to valid for the diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb index 4da32ca1b3..237299cd30 100644 --- a/actionpack/lib/action_controller/metal/rendering_controller.rb +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -1,54 +1,18 @@ module ActionController - class HashKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } - - def self.get(klass, formats, locale) - @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) - end - - attr_accessor :hash - def initialize(klass, formats, locale) - @formats, @locale = formats, locale - @hash = [formats, locale].hash - end - - alias_method :eql?, :equal? - - def inspect - "#<HashKey -- formats: #{@formats} locale: #{@locale}>" - end - end - module RenderingController extend ActiveSupport::Concern - include AbstractController::RenderingController - - module ClassMethods - def clear_template_caches! - ActionView::Partials::PartialRenderer::TEMPLATES.clear - template_cache.clear - super - end - - def template_cache - @template_cache ||= Hash.new {|h,k| h[k] = {} } - end + included do + include AbstractController::RenderingController + include AbstractController::LocalizedCache end def process_action(*) self.formats = request.formats.map {|x| x.to_sym} - - super - end - - def _determine_template(*) super end def render(options) - Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) - super self.content_type ||= options[:_template].mime_type.to_s response_body @@ -70,29 +34,19 @@ module ActionController controller_path end - def with_template_cache(name) - self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super - end - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) - 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, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] - elsif !options.key?(:partial) - options[:_template_name] = (options[:action] || action_name).to_s + if (options.keys & [:partial, :file, :template, :text, :inline]).empty? + options[:_template_name] ||= options[:action] options[:_prefix] = _prefix end super end + def format_for_text + formats.first + end + def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index ad06657f86..113c20a758 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -5,7 +5,6 @@ module ActionController #:nodoc: module RequestForgeryProtection extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base include AbstractController::Helpers, Session included do @@ -21,26 +20,26 @@ module ActionController #:nodoc: 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 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 idempotent anyway. + # 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 + # 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 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. + # 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 <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To - # make the token available through a global variable to scripts on a certain page, you could add something like this to a view: + # 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>. # - # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> - # - # 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: + # 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 @@ -57,7 +56,8 @@ module ActionController #:nodoc: # * 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 for "Expires: at end of session" + # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look + # for "Expires: at end of session" # module ClassMethods # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. @@ -76,10 +76,7 @@ module ActionController #:nodoc: # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token - before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) - if options[:secret] || options[:digest] - ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller) - end + before_filter :verify_authenticity_token, options end end @@ -88,31 +85,24 @@ module ActionController #:nodoc: def verify_authenticity_token verified_request? || raise(ActionController::InvalidAuthenticityToken) end - + # Returns true or false if a request is verified. Checks: # # * is the format restricted? By default, only HTML requests are checked. # * 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.method == :get || - request.xhr? || - !verifiable_request_format? || + !protect_against_forgery? || request.forgery_whitelisted? || form_authenticity_token == params[request_forgery_protection_token] end - - def verifiable_request_format? - !request.content_type.nil? && request.content_type.verify_request? - end - + # Sets the token value for the current session. def form_authenticity_token session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) end def protect_against_forgery? - allow_forgery_protection && request_forgery_protection_token + allow_forgery_protection end end end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index a16ed97131..c6e847ba0f 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -95,7 +95,7 @@ module ActionController #:nodoc: delegate :get?, :post?, :put?, :delete?, :to => :request # Undefine :to_json since it's defined on Object - undef_method :to_json + undef_method(:to_json) if method_defined?(:to_json) # Initializes a new responder an invoke the proper format. If the format is # not defined, call to_format. diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb index 654aa08cd3..d70f40ce7a 100644 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ b/actionpack/lib/action_controller/metal/session_management.rb @@ -1,10 +1,8 @@ module ActionController #:nodoc: module SessionManagement #:nodoc: - def self.included(base) - base.class_eval do - extend ClassMethods - end - end + extend ActiveSupport::Concern + + include ActionController::Configuration module ClassMethods # Set the session store to be used for keeping the session data between requests. @@ -35,13 +33,6 @@ module ActionController #:nodoc: session_options.merge!(options) end - # Returns the hash used to configure the session. Example use: - # - # ActionController::Base.session_options[:secure] = true # session only available over HTTPS - def session_options - @session_options ||= {} - end - def session(*args) ActiveSupport::Deprecation.warn( "Disabling sessions for a single controller has been deprecated. " + diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index d3d78e3749..500cced539 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -79,8 +79,8 @@ module ActionController #:nodoc: # 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 |c| - c.__send__ :verify_action, options + before_filter :only => options[:only], :except => options[:except] do + verify_action options end end end diff --git a/actionpack/lib/action_controller/notifications.rb b/actionpack/lib/action_controller/notifications.rb new file mode 100644 index 0000000000..1a4f29e0e2 --- /dev/null +++ b/actionpack/lib/action_controller/notifications.rb @@ -0,0 +1,10 @@ +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/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 2adf3575a7..eaed00cfb7 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -80,9 +80,8 @@ module ActionController record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1 end - record = extract_record(record_or_hash_or_array) - record = record.to_model if record.respond_to?(:to_model) - namespace = extract_namespace(record_or_hash_or_array) + record = extract_record(record_or_hash_or_array) + record = record.to_model if record.respond_to?(:to_model) args = case record_or_hash_or_array when Hash; [ record_or_hash_or_array ] @@ -105,8 +104,7 @@ module ActionController end args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} - - named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options) + named_route = build_named_route_call(record_or_hash_or_array, inflection, options) url_options = options.except(:action, :routing_type) unless url_options.empty? @@ -138,18 +136,6 @@ module ActionController EOT end - def formatted_polymorphic_url(record_or_hash, options = {}) - ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller) - options[:format] = record_or_hash.pop if Array === record_or_hash - polymorphic_url(record_or_hash, options) - end - - def formatted_polymorphic_path(record_or_hash, options = {}) - ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller) - options[:format] = record_or_hash.pop if record_or_hash === Array - polymorphic_url(record_or_hash, options.merge(:routing_type => :path)) - end - private def action_prefix(options) options[:action] ? "#{options[:action]}_" : '' @@ -159,7 +145,7 @@ module ActionController options[:routing_type] || :url end - def build_named_route_call(records, namespace, inflection, options = {}) + def build_named_route_call(records, inflection, options = {}) unless records.is_a?(Array) record = extract_record(records) route = '' @@ -169,7 +155,7 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize + string << RecordIdentifier.__send__("plural_class_name", parent).singularize string << "_" end end @@ -178,12 +164,12 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << "#{RecordIdentifier.__send__("plural_class_name", record)}" + route << RecordIdentifier.__send__("plural_class_name", record) route = route.singularize if inflection == :singular route << "_" end - action_prefix(options) + namespace + route + routing_type(options).to_s + action_prefix(options) + route + routing_type(options).to_s end def extract_record(record_or_hash_or_array) @@ -193,18 +179,5 @@ module ActionController else record_or_hash_or_array end end - - # Remove the first symbols from the array and return the url prefix - # implied by those symbols. - def extract_namespace(record_or_hash_or_array) - return "" unless record_or_hash_or_array.is_a?(Array) - - namespace_keys = [] - while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol) - namespace_keys << record_or_hash_or_array.shift - end - - namespace_keys.map {|k| "#{k}_"}.join - end end end diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb deleted file mode 100644 index 42ad12e1ea..0000000000 --- a/actionpack/lib/action_controller/routing/builder.rb +++ /dev/null @@ -1,199 +0,0 @@ -require 'active_support/core_ext/hash/except' - -module ActionController - module Routing - class RouteBuilder #:nodoc: - attr_reader :separators, :optional_separators - attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp - - def initialize - @separators = Routing::SEPARATORS - @optional_separators = %w( / ) - - @separator_regexp = /[#{Regexp.escape(separators.join)}]/ - @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/ - @interval_regexp = /(.*?)(#{separator_regexp}|$)/ - end - - # Accepts a "route path" (a string defining a route), and returns the array - # of segments that corresponds to it. Note that the segment array is only - # partially initialized--the defaults and requirements, for instance, need - # to be set separately, via the +assign_route_options+ method, and the - # <tt>optional?</tt> method for each segment will not be reliable until after - # +assign_route_options+ is called, as well. - def segments_for_route_path(path) - rest, segments = path, [] - - until rest.empty? - segment, rest = segment_for(rest) - segments << segment - end - segments - end - - # A factory method that returns a new segment instance appropriate for the - # format of the given string. - def segment_for(string) - segment = - case string - when /\A\.(:format)?\// - OptionalFormatSegment.new - when /\A:(\w+)/ - key = $1.to_sym - key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) - when /\A\*(\w+)/ - PathSegment.new($1.to_sym, :optional => true) - when /\A\?(.*?)\?/ - StaticSegment.new($1, :optional => true) - when nonseparator_regexp - StaticSegment.new($1) - when separator_regexp - DividerSegment.new($&, :optional => optional_separators.include?($&)) - end - [segment, $~.post_match] - end - - # Split the given hash of options into requirement and default hashes. The - # segments are passed alongside in order to distinguish between default values - # and requirements. - def divide_route_options(segments, options) - options = options.except(:path_prefix, :name_prefix) - - if options[:namespace] - options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}" - end - - requirements = (options.delete(:requirements) || {}).dup - defaults = (options.delete(:defaults) || {}).dup - conditions = (options.delete(:conditions) || {}).dup - - validate_route_conditions(conditions) - - path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact - options.each do |key, value| - hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements - hash[key] = value - end - - [defaults, requirements, conditions] - end - - # Takes a hash of defaults and a hash of requirements, and assigns them to - # the segments. Any unused requirements (which do not correspond to a segment) - # are returned as a hash. - def assign_route_options(segments, defaults, requirements) - route_requirements = {} # Requirements that do not belong to a segment - - segment_named = Proc.new do |key| - segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } - end - - requirements.each do |key, requirement| - segment = segment_named[key] - if segment - raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" - end - if requirement.multiline? - raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" - end - segment.regexp = requirement - else - route_requirements[key] = requirement - end - end - - defaults.each do |key, default| - segment = segment_named[key] - raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment - segment.is_optional = true - segment.default = default.to_param if default - end - - assign_default_route_options(segments) - ensure_required_segments(segments) - route_requirements - end - - # Assign default options, such as 'index' as a default for <tt>:action</tt>. This - # method must be run *after* user supplied requirements and defaults have - # been applied to the segments. - def assign_default_route_options(segments) - segments.each do |segment| - next unless segment.is_a? DynamicSegment - case segment.key - when :action - if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index' - segment.default ||= 'index' - segment.is_optional = true - end - when :id - if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ '' - segment.is_optional = true - end - end - end - end - - # Makes sure that there are no optional segments that precede a required - # segment. If any are found that precede a required segment, they are - # made required. - def ensure_required_segments(segments) - allow_optional = true - segments.reverse_each do |segment| - allow_optional &&= segment.optional? - if !allow_optional && segment.optional? - unless segment.optionality_implied? - warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." - end - segment.is_optional = false - elsif allow_optional && segment.respond_to?(:default) && segment.default - # if a segment has a default, then it is optional - segment.is_optional = true - end - end - end - - # Construct and return a route with the given path and options. - def build(path, options) - # Wrap the path with slashes - path = "/#{path}" unless path[0] == ?/ - path = "#{path}/" unless path[-1] == ?/ - - prefix = options[:path_prefix].to_s.gsub(/^\//,'') - path = "/#{prefix}#{path}" unless prefix.blank? - - segments = segments_for_route_path(path) - defaults, requirements, conditions = divide_route_options(segments, options) - requirements = assign_route_options(segments, defaults, requirements) - - # TODO: Segments should be frozen on initialize - segments.each { |segment| segment.freeze } - - route = Route.new(segments, requirements, conditions) - - if !route.significant_keys.include?(:controller) - raise ArgumentError, "Illegal route: the :controller must be specified!" - end - - route.freeze - end - - private - def validate_route_conditions(conditions) - if method = conditions[:method] - [method].flatten.each do |m| - if m == :head - raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" - end - - unless HTTP_METHODS.include?(m.to_sym) - raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}" - end - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb deleted file mode 100644 index 714cf97861..0000000000 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ /dev/null @@ -1,130 +0,0 @@ -module ActionController - module Routing - # Much of the slow performance from routes comes from the - # complexity of expiry, <tt>:requirements</tt> matching, defaults providing - # and figuring out which url pattern to use. With named routes - # we can avoid the expense of finding the right route. So if - # they've provided the right number of arguments, and have no - # <tt>:requirements</tt>, we can just build up a string and return it. - # - # To support building optimisations for other common cases, the - # generation code is separated into several classes - module Optimisation - def generate_optimisation_block(route, kind) - return "" unless route.optimise? - OPTIMISERS.inject("") do |memo, klazz| - memo << klazz.new(route, kind).source_code - memo - end - end - - class Optimiser - attr_reader :route, :kind - GLOBAL_GUARD_CONDITIONS = [ - "(!defined?(default_url_options) || default_url_options.blank?)", - "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)", - "defined?(request)", - "request" - ] - - def initialize(route, kind) - @route = route - @kind = kind - end - - def guard_conditions - ["false"] - end - - def generation_code - 'nil' - end - - def source_code - if applicable? - guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ") - "return #{generation_code} if #{guard_condition}\n" - else - "\n" - end - end - - # Temporarily disabled <tt>:url</tt> optimisation pending proper solution to - # Issues around request.host etc. - def applicable? - true - end - end - - # Given a route - # - # map.person '/people/:id' - # - # If the user calls <tt>person_url(@person)</tt>, we can simply - # return a string like "/people/#{@person.to_param}" - # rather than triggering the expensive logic in +url_for+. - class PositionalArguments < Optimiser - def guard_conditions - number_of_arguments = route.required_segment_keys.size - # if they're using foo_url(:id=>2) it's one - # argument, but we don't want to generate /foos/id2 - if number_of_arguments == 1 - ["args.size == 1", "!args.first.is_a?(Hash)"] - else - ["args.size == #{number_of_arguments}"] - end - end - - def generation_code - elements = [] - idx = 0 - - if kind == :url - elements << '#{request.protocol}' - elements << '#{request.host_with_port}' - end - - elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}' - - # The last entry in <tt>route.segments</tt> appears to *always* be a - # 'divider segment' for '/' but we have assertions to ensure that - # we don't include the trailing slashes, so skip them. - (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment| - if segment.is_a?(DynamicSegment) - elements << segment.interpolation_chunk("args[#{idx}].to_param") - idx += 1 - else - elements << segment.interpolation_chunk - end - end - %("#{elements * ''}") - end - end - - # This case is mostly the same as the positional arguments case - # above, but it supports additional query parameters as the last - # argument - class PositionalArgumentsWithAdditionalParams < PositionalArguments - def guard_conditions - ["args.size == #{route.segment_keys.size + 1}"] + - UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" } - end - - # This case uses almost the same code as positional arguments, - # but add a question mark and args.last.to_query on the end, - # unless the last arg is empty - def generation_code - super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}') - end - - # To avoid generating "http://localhost/?host=foo.example.com" we - # can't use this optimisation on routes without any segments - def applicable? - super && route.segment_keys.size > 0 - end - end - - OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams] - end - end -end diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb deleted file mode 100644 index 9bfebff0c0..0000000000 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ /dev/null @@ -1,167 +0,0 @@ -module ActionController - module Routing - # BEFORE: 0.191446860631307 ms/url - # AFTER: 0.029847304022858 ms/url - # Speed up: 6.4 times - # - # Route recognition is slow due to one-by-one iterating over - # a whole routeset (each map.resources generates at least 14 routes) - # and matching weird regexps on each step. - # - # We optimize this by skipping all URI segments that 100% sure can't - # be matched, moving deeper in a tree of routes (where node == segment) - # until first possible match is accured. In such case, we start walking - # a flat list of routes, matching them with accurate matcher. - # So, first step: search a segment tree for the first relevant index. - # Second step: iterate routes starting with that index. - # - # How tree is walked? We can do a recursive tests, but it's smarter: - # We just create a tree of if-s and elsif-s matching segments. - # - # We have segments of 3 flavors: - # 1) nil (no segment, route finished) - # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg") - # 3) const (like "/posts", "/comments") - # 4) dynamic ("/:id", "file.:size.:extension") - # - # We split incoming string into segments and iterate over them. - # When segment is nil, we drop immediately, on a current node index. - # When segment is equal to some const, we step into branch. - # If none constants matched, we step into 'dynamic' branch (it's a last). - # If we can't match anything, we drop to last index on a level. - # - # Note: we maintain the original routes order, so we finish building - # steps on a first dynamic segment. - # - # - # Example. Given the routes: - # 0 /posts/ - # 1 /posts/:id - # 2 /posts/:id/comments - # 3 /posts/blah - # 4 /users/ - # 5 /users/:id - # 6 /users/:id/profile - # - # request_uri = /users/123 - # - # There will be only 4 iterations: - # 1) segm test for /posts prefix, skip all /posts/* routes - # 2) segm test for /users/ - # 3) segm test for /users/:id - # (jump to list index = 5) - # 4) full test for /users/:id => here we are! - class RouteSet - def recognize_path(path, environment={}) - result = recognize_optimized(path, environment) and return result - - # Route was not recognized. Try to find out why (maybe wrong verb). - allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } } - - if environment[:method] && !HTTP_METHODS.include?(environment[:method]) - raise NotImplemented.new(*allows) - elsif !allows.empty? - raise MethodNotAllowed.new(*allows) - else - raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" - end - end - - def segment_tree(routes) - tree = [0] - - i = -1 - routes.each do |route| - i += 1 - # not fast, but runs only once - segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s }) - - node = tree - segments.each do |seg| - seg = :dynamic if seg && seg[0] == ?: - node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg - node = node[node.size - 1][1] - end - end - tree - end - - def generate_code(list, padding=' ', level = 0) - # a digit - return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0]) - - body = padding + "(seg = segments[#{level}]; \n" - - i = 0 - was_nil = false - list.each do |item| - if Array === item - i += 1 - start = (i == 1) - tag, sub = item - if tag == :dynamic - body += padding + "#{start ? 'if' : 'elsif'} true\n" - body += generate_code(sub, padding + " ", level + 1) - break - elsif tag == nil && !was_nil - was_nil = true - body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n" - body += generate_code(sub, padding + " ", level + 1) - else - body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n" - body += generate_code(sub, padding + " ", level + 1) - end - end - end - body += padding + "else\n" - body += padding + " #{list[0]}\n" - body += padding + "end)\n" - body - end - - # this must be really fast - def to_plain_segments(str) - str = str.dup - str.sub!(/^\/+/,'') - str.sub!(/\/+$/,'') - segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also - segments << nil - segments - end - - private - def write_recognize_optimized! - tree = segment_tree(routes) - body = generate_code(tree) - - remove_recognize_optimized! - - instance_eval %{ - def recognize_optimized(path, env) - segments = to_plain_segments(path) - index = #{body} - return nil unless index - while index < routes.size - result = routes[index].recognize(path, env) and return result - index += 1 - end - nil - end - }, '(recognize_optimized)', 1 - end - - def clear_recognize_optimized! - remove_recognize_optimized! - write_recognize_optimized! - end - - def remove_recognize_optimized! - if respond_to?(:recognize_optimized) - class << self - remove_method :recognize_optimized - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb deleted file mode 100644 index 06506435a2..0000000000 --- a/actionpack/lib/action_controller/routing/resources.rb +++ /dev/null @@ -1,685 +0,0 @@ -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/try' - -module ActionController - # == Overview - # - # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, - # is something that can be pointed at and it will respond with a representation of the data requested. - # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application - # requests XML data. - # - # RESTful design is based on the assumption that there are four generic verbs that a user of an - # application can request from a \resource (the noun). - # - # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used - # denotes the type of action that should take place. - # - # === The Different Methods and their Usage - # - # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request. - # * POST - Creation of \resources. - # * PUT - Editing of attributes on a \resource. - # * DELETE - Deletion of a \resource. - # - # === Examples - # - # # A GET request on the Posts resource is asking for all Posts - # GET /posts - # - # # A GET request on a single Post resource is asking for that particular Post - # GET /posts/1 - # - # # A POST request on the Posts resource is asking for a Post to be created with the supplied details - # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } } - # - # # A PUT request on a single Post resource is asking for a Post to be updated - # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } } - # - # # A DELETE request on a single Post resource is asking for it to be deleted - # DELETE /posts # with => { :id => 1 } - # - # By using the REST convention, users of our application can assume certain things about how the data - # is requested and how it is returned. Rails simplifies the routing part of RESTful design by - # supplying you with methods to create them in your routes.rb file. - # - # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer - module Resources - INHERITABLE_OPTIONS = :namespace, :shallow - - class Resource #:nodoc: - DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy - - attr_reader :collection_methods, :member_methods, :new_methods - attr_reader :path_prefix, :name_prefix, :path_segment - attr_reader :plural, :singular - attr_reader :options - - def initialize(entities, options) - @plural ||= entities - @singular ||= options[:singular] || plural.to_s.singularize - @path_segment = options.delete(:as) || @plural - - @options = options - - arrange_actions - add_default_actions - set_allowed_actions - set_prefixes - end - - def controller - @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}" - end - - def requirements(with_id = false) - @requirements ||= @options[:requirements] || {} - @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ } - - with_id ? @requirements.merge(@id_requirement) : @requirements - end - - def conditions - @conditions ||= @options[:conditions] || {} - end - - def path - @path ||= "#{path_prefix}/#{path_segment}" - end - - def new_path - new_action = self.options[:path_names][:new] if self.options[:path_names] - new_action ||= Base.resources_path_names[:new] - @new_path ||= "#{path}/#{new_action}" - end - - def shallow_path_prefix - @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix - end - - def member_path - @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id" - end - - def nesting_path_prefix - @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id" - end - - def shallow_name_prefix - @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix - end - - def nesting_name_prefix - "#{shallow_name_prefix}#{singular}_" - end - - def action_separator - @action_separator ||= Base.resource_action_separator - end - - def uncountable? - @singular.to_s == @plural.to_s - end - - def has_action?(action) - !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) - end - - protected - def arrange_actions - @collection_methods = arrange_actions_by_methods(options.delete(:collection)) - @member_methods = arrange_actions_by_methods(options.delete(:member)) - @new_methods = arrange_actions_by_methods(options.delete(:new)) - end - - def add_default_actions - add_default_action(member_methods, :get, :edit) - add_default_action(new_methods, :get, :new) - end - - def set_allowed_actions - only, except = @options.values_at(:only, :except) - @allowed_actions ||= {} - - if only == :all || except == :none - only = nil - except = [] - elsif only == :none || except == :all - only = [] - except = nil - end - - if only - @allowed_actions[:only] = Array(only).map {|a| a.to_sym } - elsif except - @allowed_actions[:except] = Array(except).map {|a| a.to_sym } - end - end - - def action_allowed?(action) - only, except = @allowed_actions.values_at(:only, :except) - (!only || only.include?(action)) && (!except || !except.include?(action)) - end - - def set_prefixes - @path_prefix = options.delete(:path_prefix) - @name_prefix = options.delete(:name_prefix) - end - - def arrange_actions_by_methods(actions) - (actions || {}).inject({}) do |flipped_hash, (key, value)| - (flipped_hash[value] ||= []) << key - flipped_hash - end - end - - def add_default_action(collection, method, action) - (collection[method] ||= []).unshift(action) - end - end - - class SingletonResource < Resource #:nodoc: - def initialize(entity, options) - @singular = @plural = entity - options[:controller] ||= @singular.to_s.pluralize - super - end - - alias_method :shallow_path_prefix, :path_prefix - alias_method :shallow_name_prefix, :name_prefix - alias_method :member_path, :path - alias_method :nesting_path_prefix, :path - end - - # Creates named routes for implementing verb-oriented controllers - # for a collection \resource. - # - # For example: - # - # map.resources :messages - # - # will map the following actions in the corresponding controller: - # - # class MessagesController < ActionController::Base - # # GET messages_url - # def index - # # return all messages - # end - # - # # GET new_message_url - # def new - # # return an HTML form for describing a new message - # end - # - # # POST messages_url - # def create - # # create a new message - # end - # - # # GET message_url(:id => 1) - # def show - # # find and return a specific message - # end - # - # # GET edit_message_url(:id => 1) - # def edit - # # return an HTML form for editing a specific message - # end - # - # # PUT message_url(:id => 1) - # def update - # # find and update a specific message - # end - # - # # DELETE message_url(:id => 1) - # def destroy - # # delete a specific message - # end - # end - # - # Along with the routes themselves, +resources+ generates named routes for use in - # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: - # - # Named Route Helpers - # ============ ===================================================== - # messages messages_url, hash_for_messages_url, - # messages_path, hash_for_messages_path - # - # message message_url(id), hash_for_message_url(id), - # message_path(id), hash_for_message_path(id) - # - # new_message new_message_url, hash_for_new_message_url, - # new_message_path, hash_for_new_message_path - # - # edit_message edit_message_url(id), hash_for_edit_message_url(id), - # edit_message_path(id), hash_for_edit_message_path(id) - # - # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: - # - # redirect_to :controller => 'messages', :action => 'index' - # # and - # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %> - # - # now become: - # - # redirect_to messages_url - # # and - # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically - # - # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your - # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object: - # - # <%= form_tag message_path(@message), :method => :put %> - # - # or - # - # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> - # - # or - # - # <% form_for @message do |f| %> - # - # which takes into account whether <tt>@message</tt> is a new record or not and generates the - # path and method accordingly. - # - # The +resources+ method accepts the following options to customize the resulting routes: - # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection. - # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>, - # an array of any of the previous, or <tt>:any</tt> if the method does not matter. - # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. - # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member. - # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action. - # * <tt>:controller</tt> - Specify the controller name for the routes. - # * <tt>:singular</tt> - Specify the singular name used in the member routes. - # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either - # regular expressions (which must match for the route to match) or extra parameters. For example: - # - # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' } - # - # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller. - # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes. - # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example: - # # products_path == '/productos' - # map.resources :products, :as => 'productos' do |product| - # # product_reviews_path(product) == '/productos/1234/comentarios' - # product.resources :product_reviews, :as => 'comentarios' - # end - # - # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current. - # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources. - # - # You may directly specify the routing association with +has_one+ and +has_many+ like: - # - # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] - # - # This is the same as: - # - # map.resources :notes do |notes| - # notes.resource :author - # notes.resources :comments - # notes.resources :attachments - # end - # - # * <tt>:path_names</tt> - Specify different path names for the actions. For example: - # # new_products_path == '/productos/nuevo' - # # bids_product_path(1) == '/productos/1/licitacoes' - # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } - # - # You can also set default action names from an environment, like this: - # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } - # - # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables. - # - # Weblog comments usually belong to a post, so you might use +resources+ like: - # - # map.resources :articles - # map.resources :comments, :path_prefix => '/articles/:article_id' - # - # You can nest +resources+ calls to set this automatically: - # - # map.resources :articles do |article| - # article.resources :comments - # end - # - # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>. - # - # article_comments_url(@article) - # article_comment_url(@article, @comment) - # - # article_comments_url(:article_id => @article) - # article_comment_url(:article_id => @article, :id => @comment) - # - # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: - # - # articles_comments_url(@comment.article_id, @comment) - # - # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. - # Use this if you have named routes that may clash. - # - # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' - # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' - # - # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource: - # - # map.resources :articles do |article| - # article.resources :comments, :name_prefix => nil - # end - # - # This will yield named \resources like so: - # - # comments_url(@article) - # comment_url(@article, @comment) - # - # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member - # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. - # - # The <tt>:shallow</tt> option is inherited by any nested resource(s). - # - # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: - # - # map.resources :users, :shallow => true do |user| - # user.resources :posts do |post| - # post.resources :comments - # end - # end - # # --> GET /users/1/posts (maps to the PostsController#index action as usual) - # # also adds the usual named route called "user_posts" - # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) - # # also adds the named route called "post" - # # --> GET /posts/2/comments (maps to the CommentsController#index action) - # # also adds the named route called "post_comments" - # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) - # # also adds the named route called "comment" - # - # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: - # - # map.resources :users, :has_many => { :posts => :comments }, :shallow => true - # - # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to. - # - # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a - # list of action names. By default, routes are generated for all seven actions. - # - # For example: - # - # map.resources :posts, :only => [:index, :show] do |post| - # post.resources :comments, :except => [:update, :destroy] - # end - # # --> GET /posts (maps to the PostsController#index action) - # # --> POST /posts (fails) - # # --> GET /posts/1 (maps to the PostsController#show action) - # # --> DELETE /posts/1 (fails) - # # --> POST /posts/1/comments (maps to the CommentsController#create action) - # # --> PUT /posts/1/comments/1 (fails) - # - # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. - # - # Examples: - # - # map.resources :messages, :path_prefix => "/thread/:thread_id" - # # --> GET /thread/7/messages/1 - # - # map.resources :messages, :collection => { :rss => :get } - # # --> GET /messages/rss (maps to the #rss action) - # # also adds a named route called "rss_messages" - # - # map.resources :messages, :member => { :mark => :post } - # # --> POST /messages/1/mark (maps to the #mark action) - # # also adds a named route called "mark_message" - # - # map.resources :messages, :new => { :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # - # map.resources :messages, :new => { :new => :any, :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # # --> /messages/new can be invoked via any request method - # - # map.resources :messages, :controller => "categories", - # :path_prefix => "/category/:category_id", - # :name_prefix => "category_" - # # --> GET /categories/7/messages/1 - # # has named route "category_message" - # - # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an - # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in - # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes. - def resources(*entities, &block) - options = entities.extract_options! - entities.each { |entity| map_resource(entity, options.dup, &block) } - end - - # Creates named routes for implementing verb-oriented controllers for a singleton \resource. - # A singleton \resource is global to its current context. For unnested singleton \resources, - # the \resource is global to the current user visiting the application, such as a user's - # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent - # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>. - # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>: - # - # map.resources :projects do |project| - # project.resource :project_manager - # end - # - # See +resources+ for general conventions. These are the main differences: - # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name. - # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option. - # * No default index route is created for the singleton \resource controller. - # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1') - # - # For example: - # - # map.resource :account - # - # maps these actions in the Accounts controller: - # - # class AccountsController < ActionController::Base - # # GET new_account_url - # def new - # # return an HTML form for describing the new account - # end - # - # # POST account_url - # def create - # # create an account - # end - # - # # GET account_url - # def show - # # find and return the account - # end - # - # # GET edit_account_url - # def edit - # # return an HTML form for editing the account - # end - # - # # PUT account_url - # def update - # # find and update the account - # end - # - # # DELETE account_url - # def destroy - # # delete the account - # end - # end - # - # Along with the routes themselves, +resource+ generates named routes for - # use in controllers and views. <tt>map.resource :account</tt> produces - # these named routes and helpers: - # - # Named Route Helpers - # ============ ============================================= - # account account_url, hash_for_account_url, - # account_path, hash_for_account_path - # - # new_account new_account_url, hash_for_new_account_url, - # new_account_path, hash_for_new_account_path - # - # edit_account edit_account_url, hash_for_edit_account_url, - # edit_account_path, hash_for_edit_account_path - def resource(*entities, &block) - options = entities.extract_options! - entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } - end - - private - def map_resource(entities, options = {}, &block) - resource = Resource.new(entities, options) - - with_options :controller => resource.controller do |map| - map_associations(resource, options) - - if block_given? - with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) - end - - map_collection_actions(map, resource) - map_default_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - end - end - - def map_singleton_resource(entities, options = {}, &block) - resource = SingletonResource.new(entities, options) - - with_options :controller => resource.controller do |map| - map_associations(resource, options) - - if block_given? - with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) - end - - map_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - map_default_singleton_actions(map, resource) - end - end - - def map_associations(resource, options) - map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many] - - path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" - name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" - - Array(options[:has_one]).each do |association| - resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) - end - end - - def map_has_many_associations(resource, associations, options) - case associations - when Hash - associations.each do |association,has_many| - map_has_many_associations(resource, association, options.merge(:has_many => has_many)) - end - when Array - associations.each do |association| - map_has_many_associations(resource, association, options) - end - when Symbol, String - resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) - else - end - end - - def map_collection_actions(map, resource) - resource.collection_methods.each do |method, actions| - actions.each do |action| - [method].flatten.each do |m| - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= action - - map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) - end - end - end - end - - def map_default_collection_actions(map, resource) - index_route_name = "#{resource.name_prefix}#{resource.plural}" - - if resource.uncountable? - index_route_name << "_index" - end - - map_resource_routes(map, resource, :index, resource.path, index_route_name) - map_resource_routes(map, resource, :create, resource.path, index_route_name) - end - - def map_default_singleton_actions(map, resource) - map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") - end - - def map_new_actions(map, resource) - resource.new_methods.each do |method, actions| - actions.each do |action| - route_path = resource.new_path - route_name = "new_#{resource.name_prefix}#{resource.singular}" - - unless action == :new - route_path = "#{route_path}#{resource.action_separator}#{action}" - route_name = "#{action}_#{route_name}" - end - - map_resource_routes(map, resource, action, route_path, route_name, method) - end - end - end - - def map_member_actions(map, resource) - resource.member_methods.each do |method, actions| - actions.each do |action| - [method].flatten.each do |m| - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= Base.resources_path_names[action] || action - - map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) - end - end - end - - route_path = "#{resource.shallow_name_prefix}#{resource.singular}" - map_resource_routes(map, resource, :show, resource.member_path, route_path) - map_resource_routes(map, resource, :update, resource.member_path, route_path) - map_resource_routes(map, resource, :destroy, resource.member_path, route_path) - end - - def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) - if resource.has_action?(action) - action_options = action_options_for(action, resource, method, resource_options) - formatted_route_path = "#{route_path}.:format" - - if route_name && @set.named_routes[route_name.to_sym].nil? - map.named_route(route_name, formatted_route_path, action_options) - else - map.connect(formatted_route_path, action_options) - end - end - end - - def add_conditions_for(conditions, method) - returning({:conditions => conditions.dup}) do |options| - options[:conditions][:method] = method unless method == :any - end - end - - def action_options_for(action, resource, method = nil, resource_options = {}) - default_options = { :action => action.to_s } - require_id = !resource.kind_of?(SingletonResource) - force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) - - case default_options[:action] - when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) - when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) - when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) - when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) - when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) - else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb deleted file mode 100644 index eba05a3c5a..0000000000 --- a/actionpack/lib/action_controller/routing/route.rb +++ /dev/null @@ -1,267 +0,0 @@ -require 'active_support/core_ext/object/misc' - -module ActionController - module Routing - class Route #:nodoc: - attr_accessor :segments, :requirements, :conditions, :optimise - - def initialize(segments = [], requirements = {}, conditions = {}) - @segments = segments - @requirements = requirements - @conditions = conditions - - if !significant_keys.include?(:action) && !requirements[:action] - @requirements[:action] = "index" - @significant_keys << :action - end - - # Routes cannot use the current string interpolation method - # if there are user-supplied <tt>:requirements</tt> as the interpolation - # code won't raise RoutingErrors when generating - has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp } - if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION - @optimise = false - else - @optimise = true - end - end - - # Indicates whether the routes should be optimised with the string interpolation - # version of the named routes methods. - def optimise? - @optimise && ActionController::Base::optimise_named_routes - end - - def segment_keys - segments.collect do |segment| - segment.key if segment.respond_to? :key - end.compact - end - - def required_segment_keys - required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) } - required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact - end - - # Build a query string from the keys of the given hash. If +only_keys+ - # is given (as an array), only the keys indicated will be used to build - # the query string. The query string will correctly build array parameter - # values. - def build_query_string(hash, only_keys = nil) - elements = [] - - (only_keys || hash.keys).each do |key| - if value = hash[key] - elements << value.to_query(key) - end - end - - elements.empty? ? '' : "?#{elements.sort * '&'}" - end - - # A route's parameter shell contains parameter values that are not in the - # route's path, but should be placed in the recognized hash. - # - # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: - # - # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - # - def parameter_shell - @parameter_shell ||= {}.tap do |shell| - requirements.each do |key, requirement| - shell[key] = requirement unless requirement.is_a? Regexp - end - end - end - - # Return an array containing all the keys that are used in this route. This - # includes keys that appear inside the path, and keys that have requirements - # placed upon them. - def significant_keys - @significant_keys ||= [].tap do |sk| - segments.each { |segment| sk << segment.key if segment.respond_to? :key } - sk.concat requirements.keys - sk.uniq! - end - end - - # Return a hash of key/value pairs representing the keys in the route that - # have defaults, or which are specified by non-regexp requirements. - def defaults - @defaults ||= {}.tap do |hash| - segments.each do |segment| - next unless segment.respond_to? :default - hash[segment.key] = segment.default unless segment.default.nil? - end - requirements.each do |key,req| - next if Regexp === req || req.nil? - hash[key] = req - end - end - end - - def matches_controller_and_action?(controller, action) - prepare_matching! - (@controller_requirement.nil? || @controller_requirement === controller) && - (@action_requirement.nil? || @action_requirement === action) - end - - def to_s - @to_s ||= begin - segs = segments.inject("") { |str,s| str << s.to_s } - "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect] - end - end - - # TODO: Route should be prepared and frozen on initialize - def freeze - unless frozen? - write_generation! - write_recognition! - prepare_matching! - - parameter_shell - significant_keys - defaults - to_s - end - - super - end - - def generate(options, hash, expire_on = {}) - path, hash = generate_raw(options, hash, expire_on) - append_query_string(path, hash, extra_keys(options)) - end - - def generate_extras(options, hash, expire_on = {}) - path, hash = generate_raw(options, hash, expire_on) - [path, extra_keys(options)] - end - - private - def requirement_for(key) - return requirements[key] if requirements.key? key - segments.each do |segment| - return segment.regexp if segment.respond_to?(:key) && segment.key == key - end - nil - end - - # Write and compile a +generate+ method for this Route. - def write_generation! - # Build the main body of the generation - body = "expired = false\n#{generation_extraction}\n#{generation_structure}" - - # If we have conditions that must be tested first, nest the body inside an if - body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements - args = "options, hash, expire_on = {}" - - # Nest the body inside of a def block, and then compile it. - raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash - # are the same as the keys that were recalled from the previous request. Thus, - # we can use the expire_on.keys to determine which keys ought to be used to build - # the query string. (Never use keys from the recalled request when building the - # query string.) - - raw_method - end - - # Build several lines of code that extract values from the options hash. If any - # of the values are missing or rejected then a return will be executed. - def generation_extraction - segments.collect do |segment| - segment.extraction_code - end.compact * "\n" - end - - # Produce a condition expression that will check the requirements of this route - # upon generation. - def generation_requirements - requirement_conditions = requirements.collect do |key, req| - if req.is_a? Regexp - value_regexp = Regexp.new "\\A#{req.to_s}\\Z" - "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" - else - "hash[:#{key}] == #{req.inspect}" - end - end - requirement_conditions * ' && ' unless requirement_conditions.empty? - end - - def generation_structure - segments.last.string_structure segments[0..-2] - end - - # Write and compile a +recognize+ method for this Route. - def write_recognition! - # Create an if structure to extract the params from a match if it occurs. - body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" - body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" - - # Build the method declaration and compile it - method_decl = "def recognize(path, env = {})\n#{body}\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - method_decl - end - - # Plugins may override this method to add other conditions, like checks on - # host, subdomain, and so forth. Note that changes here only affect route - # recognition, not generation. - def recognition_conditions - result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] - result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method] - result - end - - # Build the regular expression pattern that will match this route. - def recognition_pattern(wrap = true) - pattern = '' - segments.reverse_each do |segment| - pattern = segment.build_pattern pattern - end - wrap ? ("\\A" + pattern + "\\Z") : pattern - end - - # Write the code to extract the parameters from a matched route. - def recognition_extraction - next_capture = 1 - extraction = segments.collect do |segment| - x = segment.match_extraction(next_capture) - next_capture += segment.number_of_captures - x - end - extraction.compact - end - - # Generate the query string with any extra keys in the hash and append - # it to the given path, returning the new path. - def append_query_string(path, hash, query_keys = nil) - return nil unless path - query_keys ||= extra_keys(hash) - "#{path}#{build_query_string(hash, query_keys)}" - end - - # Determine which keys in the given hash are "extra". Extra keys are - # those that were not used to generate a particular route. The extra - # keys also do not include those recalled from the prior request, nor - # do they include any keys that were implied in the route (like a - # <tt>:controller</tt> that is required, but not explicitly used in the - # text of the route.) - def extra_keys(hash, recall = {}) - (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys - end - - def prepare_matching! - unless defined? @matching_prepared - @controller_requirement = requirement_for(:controller) - @action_requirement = requirement_for(:action) - @matching_prepared = true - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb deleted file mode 100644 index 5e5b22b6c2..0000000000 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/boolean/conversions' -require 'active_support/core_ext/nil/conversions' -require 'active_support/core_ext/regexp' diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb deleted file mode 100644 index 2603855476..0000000000 --- a/actionpack/lib/action_controller/routing/segments.rb +++ /dev/null @@ -1,343 +0,0 @@ -module ActionController - module Routing - class Segment #:nodoc: - RESERVED_PCHAR = ':@&=+$,;%' - SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" - if RUBY_VERSION >= '1.9' - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze - else - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze - end - - # TODO: Convert :is_optional accessor to read only - attr_accessor :is_optional - alias_method :optional?, :is_optional - - def initialize - @is_optional = false - end - - def number_of_captures - Regexp.new(regexp_chunk).number_of_captures - end - - def extraction_code - nil - end - - # Continue generating string for the prior segments. - def continue_string_structure(prior_segments) - if prior_segments.empty? - interpolation_statement(prior_segments) - else - new_priors = prior_segments[0..-2] - prior_segments.last.string_structure(new_priors) - end - end - - def interpolation_chunk - URI.escape(value, UNSAFE_PCHAR) - end - - # Return a string interpolation statement for this segment and those before it. - def interpolation_statement(prior_segments) - chunks = prior_segments.collect { |s| s.interpolation_chunk } - chunks << interpolation_chunk - "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" - end - - def string_structure(prior_segments) - optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) - end - - # Return an if condition that is true if all the prior segments can be generated. - # If there are no optional segments before this one, then nil is returned. - def all_optionals_available_condition(prior_segments) - optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact - optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" - end - - # Recognition - - def match_extraction(next_capture) - nil - end - - # Warning - - # Returns true if this segment is optional? because of a default. If so, then - # no warning will be emitted regarding this segment. - def optionality_implied? - false - end - end - - class StaticSegment < Segment #:nodoc: - attr_reader :value, :raw - alias_method :raw?, :raw - - def initialize(value = nil, options = {}) - super() - @value = value - @raw = options[:raw] if options.key?(:raw) - @is_optional = options[:optional] if options.key?(:optional) - end - - def interpolation_chunk - raw? ? value : super - end - - def regexp_chunk - chunk = Regexp.escape(value) - optional? ? Regexp.optionalize(chunk) : chunk - end - - def number_of_captures - 0 - end - - def build_pattern(pattern) - escaped = Regexp.escape(value) - if optional? && ! pattern.empty? - "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" - elsif optional? - Regexp.optionalize escaped - else - escaped + pattern - end - end - - def to_s - value - end - end - - class DividerSegment < StaticSegment #:nodoc: - def initialize(value = nil, options = {}) - super(value, {:raw => true, :optional => true}.merge(options)) - end - - def optionality_implied? - true - end - end - - class DynamicSegment < Segment #:nodoc: - attr_reader :key - - # TODO: Convert these accessors to read only - attr_accessor :default, :regexp - - def initialize(key = nil, options = {}) - super() - @key = key - @default = options[:default] if options.key?(:default) - @regexp = options[:regexp] if options.key?(:regexp) - @is_optional = true if options[:optional] || options.key?(:default) - end - - def to_s - ":#{key}" - end - - # The local variable name that the value of this segment will be extracted to. - def local_name - "#{key}_value" - end - - def extract_value - "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" - end - - def value_check - if default # Then we know it won't be nil - "#{value_regexp.inspect} =~ #{local_name}" if regexp - elsif optional? - # If we have a regexp check that the value is not given, or that it matches. - # If we have no regexp, return nil since we do not require a condition. - "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp - else # Then it must be present, and if we have a regexp, it must match too. - "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" - end - end - - def expiry_statement - "expired, hash = true, options if !expired && expire_on[:#{key}]" - end - - def extraction_code - s = extract_value - vc = value_check - s << "\nreturn [nil,nil] unless #{vc}" if vc - s << "\n#{expiry_statement}" - end - - def interpolation_chunk(value_code = local_name) - "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" - end - - def string_structure(prior_segments) - if optional? # We have a conditional to do... - # If we should not appear in the url, just write the code for the prior - # segments. This occurs if our value is the default value, or, if we are - # optional, if we have nil as our value. - "if #{local_name} == #{default.inspect}\n" + - continue_string_structure(prior_segments) + - "\nelse\n" + # Otherwise, write the code up to here - "#{interpolation_statement(prior_segments)}\nend" - else - interpolation_statement(prior_segments) - end - end - - def value_regexp - Regexp.new "\\A#{regexp.to_s}\\Z" if regexp - end - - def regexp_chunk - regexp ? regexp_string : default_regexp_chunk - end - - def regexp_string - regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})" - end - - def default_regexp_chunk - "([^#{Routing::SEPARATORS.join}]+)" - end - - def number_of_captures - regexp ? regexp.number_of_captures + 1 : 1 - end - - def build_pattern(pattern) - pattern = "#{regexp_chunk}#{pattern}" - optional? ? Regexp.optionalize(pattern) : pattern - end - - def match_extraction(next_capture) - # All non code-related keys (such as :id, :slug) are URI-unescaped as - # path parameters. - default_value = default ? default.inspect : nil - %[ - value = if (m = match[#{next_capture}]) - URI.unescape(m) - else - #{default_value} - end - params[:#{key}] = value if value - ] - end - - def optionality_implied? - [:action, :id].include? key - end - - def regexp_has_modifiers? - regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 - end - end - - class ControllerSegment < DynamicSegment #:nodoc: - def regexp_chunk - possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } - "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" - end - - # Don't URI.escape the controller name since it may contain slashes. - def interpolation_chunk(value_code = local_name) - "\#{#{value_code}.to_s}" - end - - # Make sure controller names like Admin/Content are correctly normalized to - # admin/content - def extract_value - "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" - end - - def match_extraction(next_capture) - if default - "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'" - else - "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]" - end - end - end - - class PathSegment < DynamicSegment #:nodoc: - def interpolation_chunk(value_code = local_name) - "\#{#{value_code}}" - end - - def extract_value - "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" - end - - def default - '' - end - - def default=(path) - raise RoutingError, "paths cannot have non-empty default values" unless path.blank? - end - - def match_extraction(next_capture) - "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" - end - - def default_regexp_chunk - "(.*)" - end - - def number_of_captures - regexp ? regexp.number_of_captures : 1 - end - - def optionality_implied? - true - end - - class Result < ::Array #:nodoc: - def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| URI.unescape str} - end - end - end - - # The OptionalFormatSegment allows for any resource route to have an optional - # :format, which decreases the amount of routes created by 50%. - class OptionalFormatSegment < DynamicSegment - - def initialize(key = nil, options = {}) - super(:format, {:optional => true}.merge(options)) - end - - def interpolation_chunk - "." + super - end - - def regexp_chunk - '/|(\.[^/?\.]+)?' - end - - def to_s - '(.:format)?' - end - - def extract_value - "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase" - end - - #the value should not include the period (.) - def match_extraction(next_capture) - %[ - if (m = match[#{next_capture}]) - params[:#{key}] = URI.unescape(m.from(1)) - end - ] - end - end - - end -end diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb index 178e3477a6..01a55fe930 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/testing/test_case.rb @@ -10,6 +10,13 @@ module ActionController self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) end + class Result < ::Array #:nodoc: + def to_s() join '/' end + def self.new_escaped(strings) + new strings.collect {|str| URI.unescape str} + end + end + def assign_parameters(controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = ActionController::Routing::Routes.extra_keys(parameters) @@ -18,7 +25,7 @@ module ActionController if value.is_a? Fixnum value = value.to_s elsif value.is_a? Array - value = ActionController::Routing::PathSegment::Result.new(value) + value = Result.new(value) end if extra_keys.include?(key.to_sym) diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 52b66c9303..52b66c9303 100644 --- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 11cd812695..259814a322 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -41,6 +41,8 @@ module ActionDispatch autoload :Static, 'action_dispatch/middleware/static' autoload :StringCoercion, 'action_dispatch/middleware/string_coercion' + autoload :Routing, 'action_dispatch/routing' + autoload :Assertions, 'action_dispatch/testing/assertions' autoload :Integration, 'action_dispatch/testing/integration' autoload :IntegrationTest, 'action_dispatch/testing/integration' diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index e85823d8db..c30897b32a 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -24,6 +24,7 @@ module Mime LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? } def self.[](type) + return type if type.is_a?(Type) Type.lookup_by_extension(type.to_s) end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index bff030f0e4..75be2cc260 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -97,6 +97,10 @@ module ActionDispatch end end + def forgery_whitelisted? + method == :get || xhr? || content_type.nil? || !content_type.verify_request? + end + def media_type content_type.to_s end @@ -136,19 +140,16 @@ module ActionDispatch # If-Modified-Since and If-None-Match conditions. If both headers are # supplied, both must match, or the request is not considered fresh. def fresh?(response) - case - when if_modified_since && if_none_match - not_modified?(response.last_modified) && etag_matches?(response.etag) - when if_modified_since - not_modified?(response.last_modified) - when if_none_match - etag_matches?(response.etag) - else - false - end - end + last_modified = if_modified_since + etag = if_none_match - ONLY_ALL = [Mime::ALL].freeze + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end # Returns the Mime type for the \format used in the request. # @@ -204,10 +205,6 @@ module ActionDispatch end end - def cache_format - parameters[:format] - end - # Returns true if the request's "X-Requested-With" header contains # "XMLHttpRequest". (The Prototype Javascript library sends this header with # every Ajax request.) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 3e3b473178..b3ed7c9d1a 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -270,6 +270,8 @@ module ActionDispatch # :nodoc: if control.empty? headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + elsif @cache_control[:no_cache] + headers["Cache-Control"] = "no-cache" else extras = control[:extras] max_age = control[:max_age] diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 5b9ded83dd..b9c377db2c 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,18 +1,9 @@ -require 'cgi' -require 'uri' -require 'set' +require 'active_support/core_ext/object/conversions' +require 'active_support/core_ext/boolean/conversions' +require 'active_support/core_ext/nil/conversions' +require 'active_support/core_ext/regexp' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/attribute_accessors' -require 'action_controller/routing/optimisations' -require 'action_controller/routing/routing_ext' -require 'action_controller/routing/route' -require 'action_controller/routing/segments' -require 'action_controller/routing/builder' -require 'action_controller/routing/route_set' -require 'action_controller/routing/recognition_optimisation' - -module ActionController +module ActionDispatch # == Routing # # The routing module provides URL rewriting in native Ruby. It's a way to @@ -197,7 +188,7 @@ module ActionController # # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?' # - # will glob all remaining parts of the route that were not recognized earlier. + # will glob all remaining parts of the route that were not recognized earlier. # The globbed values are in <tt>params[:path]</tt> as an array of path segments. # # == Route conditions @@ -269,6 +260,11 @@ module ActionController # Run <tt>rake routes</tt>. # module Routing + autoload :DeprecatedMapper, 'action_dispatch/routing/deprecated_mapper' + autoload :Mapper, 'action_dispatch/routing/mapper' + autoload :Route, 'action_dispatch/routing/route' + autoload :RouteSet, 'action_dispatch/routing/route_set' + SEPARATORS = %w( / . ? ) HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] @@ -281,7 +277,7 @@ module ActionController # A helper module to hold URL related helpers. module Helpers - include PolymorphicRoutes + include ActionController::PolymorphicRoutes end class << self @@ -373,13 +369,11 @@ module ActionController end end - Routes = RouteSet.new - 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)) { - ActionController::Routing::Routes.reload! if block_given? + ActionDispatch::Routing::Routes.reload! if block_given? } end diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb new file mode 100644 index 0000000000..0564ba9797 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -0,0 +1,878 @@ +module ActionDispatch + module Routing + # Mapper instances are used to build routes. The object passed to the draw + # block in config/routes.rb is a Mapper instance. + # + # Mapper instances have relatively few instance methods, in order to avoid + # clashes with named routes. + # + # == Overview + # + # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, + # is something that can be pointed at and it will respond with a representation of the data requested. + # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application + # requests XML data. + # + # RESTful design is based on the assumption that there are four generic verbs that a user of an + # application can request from a \resource (the noun). + # + # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used + # denotes the type of action that should take place. + # + # === The Different Methods and their Usage + # + # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request. + # * POST - Creation of \resources. + # * PUT - Editing of attributes on a \resource. + # * DELETE - Deletion of a \resource. + # + # === Examples + # + # # A GET request on the Posts resource is asking for all Posts + # GET /posts + # + # # A GET request on a single Post resource is asking for that particular Post + # GET /posts/1 + # + # # A POST request on the Posts resource is asking for a Post to be created with the supplied details + # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } } + # + # # A PUT request on a single Post resource is asking for a Post to be updated + # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } } + # + # # A DELETE request on a single Post resource is asking for it to be deleted + # DELETE /posts # with => { :id => 1 } + # + # By using the REST convention, users of our application can assume certain things about how the data + # is requested and how it is returned. Rails simplifies the routing part of RESTful design by + # supplying you with methods to create them in your routes.rb file. + # + # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer + class DeprecatedMapper #:doc: + def initialize(set) #:nodoc: + @set = set + end + + # Create an unnamed route with the provided +path+ and +options+. See + # ActionDispatch::Routing for an introduction to routes. + def connect(path, options = {}) + options = options.dup + + if conditions = options.delete(:conditions) + conditions = conditions.dup + method = [conditions.delete(:method)].flatten.compact + method.map! { |m| + m = m.to_s.upcase + + if m == "HEAD" + raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" + end + + unless HTTP_METHODS.include?(m.downcase.to_sym) + raise ArgumentError, "Invalid HTTP method specified in route conditions" + end + + m + } + + if method.length > 1 + method = Regexp.union(*method) + elsif method.length == 1 + method = method.first + else + method = nil + end + end + + path_prefix = options.delete(:path_prefix) + name_prefix = options.delete(:name_prefix) + namespace = options.delete(:namespace) + + name = options.delete(:_name) + name = "#{name_prefix}#{name}" if name_prefix + + requirements = options.delete(:requirements) || {} + defaults = options.delete(:defaults) || {} + options.each do |k, v| + if v.is_a?(Regexp) + if value = options.delete(k) + requirements[k.to_sym] = value + end + else + value = options.delete(k) + defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param + end + end + + requirements.each do |_, requirement| + if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + if requirement.multiline? + raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + end + end + + possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) } + requirements[:controller] ||= Regexp.union(*possible_names) + + if defaults[:controller] + defaults[:action] ||= 'index' + defaults[:controller] = defaults[:controller].to_s + defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace + end + + if defaults[:action] + defaults[:action] = defaults[:action].to_s + end + + if path.is_a?(String) + path = "#{path_prefix}/#{path}" if path_prefix + path = path.gsub('.:format', '(.:format)') + path = optionalize_trailing_dynamic_segments(path, requirements, defaults) + glob = $1.to_sym if path =~ /\/\*(\w+)$/ + path = ::Rack::Mount::Utils.normalize_path(path) + + if glob && !defaults[glob].blank? + raise ActionController::RoutingError, "paths cannot have non-empty default values" + end + end + + app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob) + + conditions = {} + conditions[:request_method] = method if method + conditions[:path_info] = path if path + + @set.add_route(app, conditions, requirements, defaults, name) + end + + def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc: + path = (path =~ /^\//) ? path.dup : "/#{path}" + optional, segments = true, [] + + required_segments = requirements.keys + required_segments -= defaults.keys.compact + + old_segments = path.split('/') + old_segments.shift + length = old_segments.length + + old_segments.reverse.each_with_index do |segment, index| + required_segments.each do |required| + if segment =~ /#{required}/ + optional = false + break + end + end + + if optional + if segment == ":id" && segments.include?(":action") + optional = false + elsif segment == ":controller" || segment == ":action" || segment == ":id" + # Ignore + elsif !(segment =~ /^:\w+$/) && + !(segment =~ /^:\w+\(\.:format\)$/) + optional = false + elsif segment =~ /^:(\w+)$/ + if defaults.has_key?($1.to_sym) + defaults.delete($1.to_sym) + else + optional = false + end + end + end + + if optional && index < length - 1 + segments.unshift('(/', segment) + segments.push(')') + elsif optional + segments.unshift('/(', segment) + segments.push(')') + else + segments.unshift('/', segment) + end + end + + segments.join + end + private :optionalize_trailing_dynamic_segments + + # Creates a named route called "root" for matching the root level request. + def root(options = {}) + if options.is_a?(Symbol) + if source_route = @set.named_routes.routes[options] + options = source_route.defaults.merge({ :conditions => source_route.conditions }) + end + end + named_route("root", '', options) + end + + def named_route(name, path, options = {}) #:nodoc: + options[:_name] = name + connect(path, options) + end + + # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. + # Example: + # + # map.namespace(:admin) do |admin| + # admin.resources :products, + # :has_many => [ :tags, :images, :variants ] + # end + # + # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. + # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for + # Admin::TagsController. + def namespace(name, options = {}, &block) + if options[:namespace] + with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) + else + with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) + end + end + + def method_missing(route_name, *args, &proc) #:nodoc: + super unless args.length >= 1 && proc.nil? + named_route(route_name, *args) + end + + INHERITABLE_OPTIONS = :namespace, :shallow + + class Resource #:nodoc: + DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy + + attr_reader :collection_methods, :member_methods, :new_methods + attr_reader :path_prefix, :name_prefix, :path_segment + attr_reader :plural, :singular + attr_reader :options + + def initialize(entities, options) + @plural ||= entities + @singular ||= options[:singular] || plural.to_s.singularize + @path_segment = options.delete(:as) || @plural + + @options = options + + arrange_actions + add_default_actions + set_allowed_actions + set_prefixes + end + + def controller + @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}" + end + + def requirements(with_id = false) + @requirements ||= @options[:requirements] || {} + @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ } + + with_id ? @requirements.merge(@id_requirement) : @requirements + end + + def conditions + @conditions ||= @options[:conditions] || {} + end + + def path + @path ||= "#{path_prefix}/#{path_segment}" + end + + def new_path + new_action = self.options[:path_names][:new] if self.options[:path_names] + new_action ||= ActionController::Base.resources_path_names[:new] + @new_path ||= "#{path}/#{new_action}" + end + + def shallow_path_prefix + @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix + end + + def member_path + @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id" + end + + def nesting_path_prefix + @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id" + end + + def shallow_name_prefix + @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix + end + + def nesting_name_prefix + "#{shallow_name_prefix}#{singular}_" + end + + def action_separator + @action_separator ||= ActionController::Base.resource_action_separator + end + + def uncountable? + @singular.to_s == @plural.to_s + end + + def has_action?(action) + !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + end + + protected + def arrange_actions + @collection_methods = arrange_actions_by_methods(options.delete(:collection)) + @member_methods = arrange_actions_by_methods(options.delete(:member)) + @new_methods = arrange_actions_by_methods(options.delete(:new)) + end + + def add_default_actions + add_default_action(member_methods, :get, :edit) + add_default_action(new_methods, :get, :new) + end + + def set_allowed_actions + only, except = @options.values_at(:only, :except) + @allowed_actions ||= {} + + if only == :all || except == :none + only = nil + except = [] + elsif only == :none || except == :all + only = [] + except = nil + end + + if only + @allowed_actions[:only] = Array(only).map {|a| a.to_sym } + elsif except + @allowed_actions[:except] = Array(except).map {|a| a.to_sym } + end + end + + def action_allowed?(action) + only, except = @allowed_actions.values_at(:only, :except) + (!only || only.include?(action)) && (!except || !except.include?(action)) + end + + def set_prefixes + @path_prefix = options.delete(:path_prefix) + @name_prefix = options.delete(:name_prefix) + end + + def arrange_actions_by_methods(actions) + (actions || {}).inject({}) do |flipped_hash, (key, value)| + (flipped_hash[value] ||= []) << key + flipped_hash + end + end + + def add_default_action(collection, method, action) + (collection[method] ||= []).unshift(action) + end + end + + class SingletonResource < Resource #:nodoc: + def initialize(entity, options) + @singular = @plural = entity + options[:controller] ||= @singular.to_s.pluralize + super + end + + alias_method :shallow_path_prefix, :path_prefix + alias_method :shallow_name_prefix, :name_prefix + alias_method :member_path, :path + alias_method :nesting_path_prefix, :path + end + + # Creates named routes for implementing verb-oriented controllers + # for a collection \resource. + # + # For example: + # + # map.resources :messages + # + # will map the following actions in the corresponding controller: + # + # class MessagesController < ActionController::Base + # # GET messages_url + # def index + # # return all messages + # end + # + # # GET new_message_url + # def new + # # return an HTML form for describing a new message + # end + # + # # POST messages_url + # def create + # # create a new message + # end + # + # # GET message_url(:id => 1) + # def show + # # find and return a specific message + # end + # + # # GET edit_message_url(:id => 1) + # def edit + # # return an HTML form for editing a specific message + # end + # + # # PUT message_url(:id => 1) + # def update + # # find and update a specific message + # end + # + # # DELETE message_url(:id => 1) + # def destroy + # # delete a specific message + # end + # end + # + # Along with the routes themselves, +resources+ generates named routes for use in + # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: + # + # Named Route Helpers + # ============ ===================================================== + # messages messages_url, hash_for_messages_url, + # messages_path, hash_for_messages_path + # + # message message_url(id), hash_for_message_url(id), + # message_path(id), hash_for_message_path(id) + # + # new_message new_message_url, hash_for_new_message_url, + # new_message_path, hash_for_new_message_path + # + # edit_message edit_message_url(id), hash_for_edit_message_url(id), + # edit_message_path(id), hash_for_edit_message_path(id) + # + # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: + # + # redirect_to :controller => 'messages', :action => 'index' + # # and + # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %> + # + # now become: + # + # redirect_to messages_url + # # and + # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically + # + # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your + # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object: + # + # <%= form_tag message_path(@message), :method => :put %> + # + # or + # + # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> + # + # or + # + # <% form_for @message do |f| %> + # + # which takes into account whether <tt>@message</tt> is a new record or not and generates the + # path and method accordingly. + # + # The +resources+ method accepts the following options to customize the resulting routes: + # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection. + # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>, + # an array of any of the previous, or <tt>:any</tt> if the method does not matter. + # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. + # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member. + # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action. + # * <tt>:controller</tt> - Specify the controller name for the routes. + # * <tt>:singular</tt> - Specify the singular name used in the member routes. + # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either + # regular expressions (which must match for the route to match) or extra parameters. For example: + # + # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' } + # + # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller. + # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes. + # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example: + # # products_path == '/productos' + # map.resources :products, :as => 'productos' do |product| + # # product_reviews_path(product) == '/productos/1234/comentarios' + # product.resources :product_reviews, :as => 'comentarios' + # end + # + # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current. + # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources. + # + # You may directly specify the routing association with +has_one+ and +has_many+ like: + # + # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] + # + # This is the same as: + # + # map.resources :notes do |notes| + # notes.resource :author + # notes.resources :comments + # notes.resources :attachments + # end + # + # * <tt>:path_names</tt> - Specify different path names for the actions. For example: + # # new_products_path == '/productos/nuevo' + # # bids_product_path(1) == '/productos/1/licitacoes' + # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } + # + # You can also set default action names from an environment, like this: + # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } + # + # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables. + # + # Weblog comments usually belong to a post, so you might use +resources+ like: + # + # map.resources :articles + # map.resources :comments, :path_prefix => '/articles/:article_id' + # + # You can nest +resources+ calls to set this automatically: + # + # map.resources :articles do |article| + # article.resources :comments + # end + # + # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>. + # + # article_comments_url(@article) + # article_comment_url(@article, @comment) + # + # article_comments_url(:article_id => @article) + # article_comment_url(:article_id => @article, :id => @comment) + # + # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: + # + # articles_comments_url(@comment.article_id, @comment) + # + # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. + # Use this if you have named routes that may clash. + # + # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' + # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' + # + # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource: + # + # map.resources :articles do |article| + # article.resources :comments, :name_prefix => nil + # end + # + # This will yield named \resources like so: + # + # comments_url(@article) + # comment_url(@article, @comment) + # + # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member + # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. + # + # The <tt>:shallow</tt> option is inherited by any nested resource(s). + # + # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: + # + # map.resources :users, :shallow => true do |user| + # user.resources :posts do |post| + # post.resources :comments + # end + # end + # # --> GET /users/1/posts (maps to the PostsController#index action as usual) + # # also adds the usual named route called "user_posts" + # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) + # # also adds the named route called "post" + # # --> GET /posts/2/comments (maps to the CommentsController#index action) + # # also adds the named route called "post_comments" + # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) + # # also adds the named route called "comment" + # + # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: + # + # map.resources :users, :has_many => { :posts => :comments }, :shallow => true + # + # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to. + # + # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a + # list of action names. By default, routes are generated for all seven actions. + # + # For example: + # + # map.resources :posts, :only => [:index, :show] do |post| + # post.resources :comments, :except => [:update, :destroy] + # end + # # --> GET /posts (maps to the PostsController#index action) + # # --> POST /posts (fails) + # # --> GET /posts/1 (maps to the PostsController#show action) + # # --> DELETE /posts/1 (fails) + # # --> POST /posts/1/comments (maps to the CommentsController#create action) + # # --> PUT /posts/1/comments/1 (fails) + # + # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. + # + # Examples: + # + # map.resources :messages, :path_prefix => "/thread/:thread_id" + # # --> GET /thread/7/messages/1 + # + # map.resources :messages, :collection => { :rss => :get } + # # --> GET /messages/rss (maps to the #rss action) + # # also adds a named route called "rss_messages" + # + # map.resources :messages, :member => { :mark => :post } + # # --> POST /messages/1/mark (maps to the #mark action) + # # also adds a named route called "mark_message" + # + # map.resources :messages, :new => { :preview => :post } + # # --> POST /messages/new/preview (maps to the #preview action) + # # also adds a named route called "preview_new_message" + # + # map.resources :messages, :new => { :new => :any, :preview => :post } + # # --> POST /messages/new/preview (maps to the #preview action) + # # also adds a named route called "preview_new_message" + # # --> /messages/new can be invoked via any request method + # + # map.resources :messages, :controller => "categories", + # :path_prefix => "/category/:category_id", + # :name_prefix => "category_" + # # --> GET /categories/7/messages/1 + # # has named route "category_message" + # + # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an + # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in + # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes. + def resources(*entities, &block) + options = entities.extract_options! + entities.each { |entity| map_resource(entity, options.dup, &block) } + end + + # Creates named routes for implementing verb-oriented controllers for a singleton \resource. + # A singleton \resource is global to its current context. For unnested singleton \resources, + # the \resource is global to the current user visiting the application, such as a user's + # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent + # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>. + # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>: + # + # map.resources :projects do |project| + # project.resource :project_manager + # end + # + # See +resources+ for general conventions. These are the main differences: + # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name. + # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option. + # * No default index route is created for the singleton \resource controller. + # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1') + # + # For example: + # + # map.resource :account + # + # maps these actions in the Accounts controller: + # + # class AccountsController < ActionController::Base + # # GET new_account_url + # def new + # # return an HTML form for describing the new account + # end + # + # # POST account_url + # def create + # # create an account + # end + # + # # GET account_url + # def show + # # find and return the account + # end + # + # # GET edit_account_url + # def edit + # # return an HTML form for editing the account + # end + # + # # PUT account_url + # def update + # # find and update the account + # end + # + # # DELETE account_url + # def destroy + # # delete the account + # end + # end + # + # Along with the routes themselves, +resource+ generates named routes for + # use in controllers and views. <tt>map.resource :account</tt> produces + # these named routes and helpers: + # + # Named Route Helpers + # ============ ============================================= + # account account_url, hash_for_account_url, + # account_path, hash_for_account_path + # + # new_account new_account_url, hash_for_new_account_url, + # new_account_path, hash_for_new_account_path + # + # edit_account edit_account_url, hash_for_edit_account_url, + # edit_account_path, hash_for_edit_account_path + def resource(*entities, &block) + options = entities.extract_options! + entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } + end + + private + def map_resource(entities, options = {}, &block) + resource = Resource.new(entities, options) + + with_options :controller => resource.controller do |map| + map_associations(resource, options) + + if block_given? + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) + end + + map_collection_actions(map, resource) + map_default_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) + end + end + + def map_singleton_resource(entities, options = {}, &block) + resource = SingletonResource.new(entities, options) + + with_options :controller => resource.controller do |map| + map_associations(resource, options) + + if block_given? + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) + end + + map_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) + map_default_singleton_actions(map, resource) + end + end + + def map_associations(resource, options) + map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many] + + path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" + name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" + + Array(options[:has_one]).each do |association| + resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) + end + end + + def map_has_many_associations(resource, associations, options) + case associations + when Hash + associations.each do |association,has_many| + map_has_many_associations(resource, association, options.merge(:has_many => has_many)) + end + when Array + associations.each do |association| + map_has_many_associations(resource, association, options) + end + when Symbol, String + resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) + else + end + end + + def map_collection_actions(map, resource) + resource.collection_methods.each do |method, actions| + actions.each do |action| + [method].flatten.each do |m| + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= action + + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) + end + end + end + end + + def map_default_collection_actions(map, resource) + index_route_name = "#{resource.name_prefix}#{resource.plural}" + + if resource.uncountable? + index_route_name << "_index" + end + + map_resource_routes(map, resource, :index, resource.path, index_route_name) + map_resource_routes(map, resource, :create, resource.path, index_route_name) + end + + def map_default_singleton_actions(map, resource) + map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") + end + + def map_new_actions(map, resource) + resource.new_methods.each do |method, actions| + actions.each do |action| + route_path = resource.new_path + route_name = "new_#{resource.name_prefix}#{resource.singular}" + + unless action == :new + route_path = "#{route_path}#{resource.action_separator}#{action}" + route_name = "#{action}_#{route_name}" + end + + map_resource_routes(map, resource, action, route_path, route_name, method) + end + end + end + + def map_member_actions(map, resource) + resource.member_methods.each do |method, actions| + actions.each do |action| + [method].flatten.each do |m| + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= ActionController::Base.resources_path_names[action] || action + + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) + end + end + end + + route_path = "#{resource.shallow_name_prefix}#{resource.singular}" + map_resource_routes(map, resource, :show, resource.member_path, route_path) + map_resource_routes(map, resource, :update, resource.member_path, route_path) + map_resource_routes(map, resource, :destroy, resource.member_path, route_path) + end + + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) + if resource.has_action?(action) + action_options = action_options_for(action, resource, method, resource_options) + formatted_route_path = "#{route_path}.:format" + + if route_name && @set.named_routes[route_name.to_sym].nil? + map.named_route(route_name, formatted_route_path, action_options) + else + map.connect(formatted_route_path, action_options) + end + end + end + + def add_conditions_for(conditions, method) + returning({:conditions => conditions.dup}) do |options| + options[:conditions][:method] = method unless method == :any + end + end + + def action_options_for(action, resource, method = nil, resource_options = {}) + default_options = { :action => action.to_s } + require_id = !resource.kind_of?(SingletonResource) + force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) + + case default_options[:action] + when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) + when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) + when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) + when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) + when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) + else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb new file mode 100644 index 0000000000..d6d822842b --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -0,0 +1,315 @@ +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 + end + + resource = resources.pop + + if @scope[:scope_level] == :resources + member do + resource(resource, options, &block) + end + return self + end + + controller(resource) do + namespace(resource) do + with_scope_level(:resource) do + yield if block_given? + + get "", :to => :show + post "", :to => :create + put "", :to => :update + delete "", :to => :destory + end + end + end + + self + end + + def resources(*resources, &block) + options = resources.last.is_a?(Hash) ? resources.pop : {} + + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resources(r, options) } + return self + end + + resource = resources.pop + + if @scope[:scope_level] == :resources + member do + resources(resource, options, &block) + end + return self + end + + controller(resource) do + namespace(resource) do + with_scope_level(:resources) do + yield if block_given? + + member do + get "", :to => :show + put "", :to => :update + delete "", :to => :destory + get "edit", :to => :edit + end + + collection do + get "", :to => :index + post "", :to => :create + get "new", :to => :new + end + end + end + end + + self + end + + def collection + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use collection outside resources scope" + end + + with_scope_level(:collection) do + yield + end + end + + def member + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use member outside resources scope" + end + + with_scope_level(:member) do + scope(":id") do + yield + end + end + end + + def match(*args) + 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 + + if @scope[:scope_level] == :resources + raise ArgumentError, "can't define route directly in resources scope" + end + + super + end + + private + def with_scope_level(kind) + old, @scope[:scope_level] = @scope[:scope_level], kind + yield + ensure + @scope[:scope_level] = old + end + end + + module Scoping + def scope(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + + constraints = options.delete(:constraints) || {} + unless constraints.is_a?(Hash) + block, constraints = constraints, {} + end + constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) + blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] + + 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[:constraints] = constraints + end + + def controller(controller) + scope(controller.to_sym) { yield } + end + + def namespace(path) + scope(path.to_s) { yield } + end + + def constraints(constraints = {}) + scope(:constraints => constraints) { yield } + end + end + + class Constraints + def initialize(app, constraints = []) + @app, @constraints = app, constraints + end + + def call(env) + req = Rack::Request.new(env) + + @constraints.each { |constraint| + if constraint.respond_to?(:matches?) && !constraint.matches?(req) + return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE + elsif constraint.respond_to?(:call) && !constraint.call(req) + return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE + end + } + + @app.call(env) + end + end + + def initialize(set) + @set = set + @scope = {} + + extend Scoping + extend Resources + end + + 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 match(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + + if args.length > 1 + args.each { |path| match(path, options) } + return self + end + + if args.first.is_a?(Symbol) + return match(args.first.to_s, options.merge(:to => args.first.to_sym)) + end + + path = args.first + + options = (@scope[:options] || {}).merge(options) + conditions, defaults = {}, {} + + path = nil if path == "" + path = Rack::Mount::Utils.normalize_path(path) if path + path = "#{@scope[:path]}#{path}" if @scope[:path] + + raise ArgumentError, "path is required" unless path + + constraints = options[:constraints] || {} + unless constraints.is_a?(Hash) + block, constraints = constraints, {} + 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 + + 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) + + if via = options[:via] + via = Array(via).map { |m| m.to_s.upcase } + conditions[:request_method] = Regexp.union(*via) + end + + defaults[:controller] = @scope[:controller].to_s if @scope[:controller] + + 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 + 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" + end + unless defaults.include?(:action) || segment_keys.include?("action") + raise ArgumentError, "missing :action" + end + end + + app = Constraints.new(app, blocks) if blocks.any? + @set.add_route(app, conditions, requirements, defaults, options[:as]) + + 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 + + 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 + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb new file mode 100644 index 0000000000..f1431e7a37 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -0,0 +1,49 @@ +module ActionDispatch + module Routing + class Route #:nodoc: + attr_reader :app, :conditions, :defaults, :name + attr_reader :path, :requirements + + def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + @app = app + @defaults = defaults + @name = name + + @requirements = requirements.merge(defaults) + @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp) + @requirements.delete_if { |k, v| + v == Regexp.compile("[^#{SEPARATORS.join}]+") + } + + if path = conditions[:path_info] + @path = path + conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS) + end + + @conditions = conditions.inject({}) { |h, (k, v)| + h[k] = Rack::Mount::RegexpWithNamedGroups.new(v) + h + } + end + + def verb + if method = conditions[:request_method] + case method + when Regexp + method.source.upcase + else + method.to_s.upcase + end + end + end + + def segment_keys + @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym } + end + + def to_a + [@app, @conditions, @defaults, @name] + end + end + end +end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 25fdbf480e..93617e826d 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,69 +1,58 @@ -module ActionController +require 'rack/mount' +require 'forwardable' + +module ActionDispatch module Routing class RouteSet #:nodoc: - # Mapper instances are used to build routes. The object passed to the draw - # block in config/routes.rb is a Mapper instance. - # - # Mapper instances have relatively few instance methods, in order to avoid - # clashes with named routes. - class Mapper #:doc: - include ActionController::Resources - - def initialize(set) #:nodoc: - @set = set - end + NotFound = lambda { |env| + raise ActionController::RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}" + } - # Create an unnamed route with the provided +path+ and +options+. See - # ActionController::Routing for an introduction to routes. - def connect(path, options = {}) - @set.add_route(path, options) - end + PARAMETERS_KEY = 'action_dispatch.request.path_parameters' - # Creates a named route called "root" for matching the root level request. - def root(options = {}) - if options.is_a?(Symbol) - if source_route = @set.named_routes.routes[options] - options = source_route.defaults.merge({ :conditions => source_route.conditions }) - end - end - named_route("root", '', options) + class Dispatcher + def initialize(options = {}) + defaults = options[:defaults] + @glob_param = options.delete(:glob) end - def named_route(name, path, options = {}) #:nodoc: - @set.add_named_route(name, path, options) - end + def call(env) + params = env[PARAMETERS_KEY] + merge_default_action!(params) + split_glob_param!(params) if @glob_param + params.each { |key, value| params[key] = URI.unescape(value) if value.is_a?(String) } - # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. - # Example: - # - # map.namespace(:admin) do |admin| - # admin.resources :products, - # :has_many => [ :tags, :images, :variants ] - # end - # - # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. - # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for - # Admin::TagsController. - def namespace(name, options = {}, &block) - if options[:namespace] - with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) + if env['action_controller.recognize'] + [200, {}, params] else - with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) + controller = controller(params) + controller.action(params[:action]).call(env) end end - def method_missing(route_name, *args, &proc) #:nodoc: - super unless args.length >= 1 && proc.nil? - @set.add_named_route(route_name, *args) - 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 + + def split_glob_param!(params) + params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) } + end end + # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. class NamedRouteCollection #:nodoc: include Enumerable - include ActionController::Routing::Optimisation attr_reader :routes, :helpers def initialize @@ -175,8 +164,6 @@ module ActionController named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks def #{selector}(*args) # def users_url(*args) # - #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)} - # opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first args.first || {} # args.first || {} else # else @@ -216,28 +203,18 @@ module ActionController clear! end - # Subclasses and plugins may override this method to specify a different - # RouteBuilder instance, so that other route DSL's can be created. - def builder - @builder ||= RouteBuilder.new - end - - def draw + def draw(&block) clear! - yield Mapper.new(self) + Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block) + @set.add_route(NotFound) install_helpers + @set.freeze end def clear! routes.clear named_routes.clear - - @combined_regexp = nil - @routes_by_controller = nil - - # This will force routing/recognition_optimization.rb - # to refresh optimisations. - clear_recognize_optimized! + @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY) end def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) @@ -257,7 +234,7 @@ module ActionController def configuration_file=(path) add_configuration_file(path) end - + # Deprecated accessor def configuration_file configuration_files @@ -296,31 +273,26 @@ module ActionController def routes_changed_at routes_changed_at = nil - + 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 + routes_changed_at = config_changed_at end end - + routes_changed_at end - def add_route(path, options = {}) - options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) } - route = builder.build(path, options) + def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + route = Route.new(app, conditions, requirements, defaults, name) + @set.add_route(*route) + named_routes[name] = route if name routes << route route end - def add_named_route(name, path, options = {}) - # TODO - is options EVER used? - name = options[:name_prefix] + name.to_s if options[:name_prefix] - named_routes[name.to_sym] = add_route(path, options) - end - def options_as_params(options) # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -356,24 +328,29 @@ module ActionController generate(options, recall, :generate_extras) end - def generate(options, recall = {}, method=:generate) - named_route_name = options.delete(:use_route) - generate_all = options.delete(:generate_all) - if named_route_name - named_route = named_routes[named_route_name] - options = named_route.parameter_shell.merge(options) - end + def generate(options, recall = {}, method = :generate) + options, recall = options.dup, recall.dup + named_route = options.delete(:use_route) options = options_as_params(options) expire_on = build_expiry(options, recall) - if options[:controller] - options[:controller] = options[:controller].to_s + recall[:action] ||= 'index' if options[:controller] || recall[:controller] + + if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller]) + options[:controller] = recall.delete(:controller) + + if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action]) + options[:action] = recall.delete(:action) + + if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id]) + options[:id] = recall.delete(:id) + end + end end - # if the controller has changed, make sure it changes relative to the - # current controller module, if any. In other words, if we're currently - # on admin/get, and the new controller is 'set', the new controller - # should really be admin/set. + + options[:controller] = options[:controller].to_s if options[:controller] + if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ old_parts = recall[:controller].split('/') new_parts = options[:controller].split('/') @@ -381,63 +358,54 @@ module ActionController options[:controller] = parts.join('/') end - # drop the leading '/' on the controller name options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ - merged = recall.merge(options) - - if named_route - path = named_route.generate(options, merged, expire_on) - if path.nil? - raise_named_route_error(options, named_route, named_route_name) - else - return path - end - else - merged[:action] ||= 'index' - options[:action] ||= 'index' - - controller = merged[:controller] - action = merged[:action] - raise RoutingError, "Need controller and action!" unless controller && action - - if generate_all - # Used by caching to expire all paths for a resource - return routes.collect do |route| - route.__send__(method, options, merged, expire_on) - end.compact - end - - # don't use the recalled keys when determining which routes to check - routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] - - routes.each_with_index do |route, index| - results = route.__send__(method, options, merged, expire_on) - if results && (!results.is_a?(Array) || results.first) - return results - end - end + merged = options.merge(recall) + if options.has_key?(:action) && options[:action].nil? + options.delete(:action) + recall[:action] = 'index' end - - raise RoutingError, "No route matches #{options.inspect}" - end - - # try to give a helpful error message when named route generation fails - def raise_named_route_error(options, named_route, named_route_name) - diff = named_route.requirements.diff(options) - unless diff.empty? - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" + recall[:action] = options.delete(:action) if options[:action] == 'index' + + path = _uri(named_route, options, recall) + 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] + elsif path + path else - required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) } - required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?" + raise ActionController::RoutingError, "No route matches #{options.inspect}" end + rescue Rack::Mount::RoutingError + raise ActionController::RoutingError, "No route matches #{options.inspect}" end def call(env) - request = ActionDispatch::Request.new(env) - app = Routing::Routes.recognize(request) - app.action(request.parameters[:action] || 'index').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) @@ -446,33 +414,19 @@ module ActionController "#{params[:controller].to_s.camelize}Controller".constantize end - def recognize_path(path, environment={}) - raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path." - end + def recognize_path(path, environment = {}, rescue_error = true) + method = (environment[:method] || "GET").to_s.upcase - def routes_by_controller - @routes_by_controller ||= Hash.new do |controller_hash, controller| - controller_hash[controller] = Hash.new do |action_hash, action| - action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) - end - end + begin + env = Rack::MockRequest.env_for(path, {:method => method}) + rescue URI::InvalidURIError => e + raise ActionController::RoutingError, e.message end - end - - def routes_for(options, merged, expire_on) - raise "Need controller and action!" unless controller && action - controller = merged[:controller] - merged = options if expire_on[:controller] - action = merged[:action] || 'index' - routes_by_controller[controller][action][merged.keys][1] - end - - def routes_for_controller_and_action_and_keys(controller, action, keys) - routes.select do |route| - route.matches_controller_and_action? controller, action - end + env['action_controller.recognize'] = true + env['action_controller.rescue_error'] = rescue_error + status, headers, body = call(env) + body end # Subclasses and plugins may override this method to extract further attributes @@ -480,6 +434,59 @@ module ActionController def extract_request_environment(request) { :method => request.method } end + + private + def _uri(named_route, params, recall) + params = URISegment.wrap_values(params) + recall = URISegment.wrap_values(recall) + + unless result = @set.generate(:path_info, named_route, params, recall) + return + end + + uri, params = result + params.each do |k, v| + if v._value + params[k] = v._value + else + params.delete(k) + end + end + + uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any? + uri + end + + class URISegment < Struct.new(:_value, :_escape) + EXCLUDED = [:controller] + + def self.wrap_values(hash) + hash.inject({}) { |h, (k, v)| + h[k] = new(v, !EXCLUDED.include?(k.to_sym)) + h + } + end + + extend Forwardable + def_delegators :_value, :==, :eql?, :hash + + def to_param + @to_param ||= begin + if _value.is_a?(Array) + _value.map { |v| _escaped(v) }.join('/') + else + _escaped(_value) + end + end + end + alias_method :to_s, :to_param + + private + def _escaped(value) + v = value.respond_to?(:to_param) ? value.to_param : value + _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s + end + end end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 58ebe94a5b..40d6f97b2a 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -490,7 +490,7 @@ module ActionDispatch def self.app # DEPRECATE Rails application fallback # This should be set by the initializer - @@app || (defined?(Rails.application) && Rails.application.new) || nil + @@app || (defined?(Rails.application) && Rails.application) || nil end def self.app=(app) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 84e4103280..c33695770f 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -187,11 +187,11 @@ module ActionView #:nodoc: def reset_formats(formats) @formats = formats - if defined?(ActionController) + if defined?(AbstractController::HashKey) # This is expensive, but we need to reset this when the format is updated, # which currently only happens Thread.current[:format_locale_key] = - ActionController::HashKey.get(self.class, formats, I18n.locale) + AbstractController::HashKey.get(self.class, formats, I18n.locale) end end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index d63e8602f1..3d088678fc 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -1,10 +1,11 @@ +require 'active_support/benchmarkable' + module ActionView #:nodoc: module Helpers #:nodoc: autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper' autoload :AjaxHelper, 'action_view/helpers/ajax_helper' autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper' autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper' - autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper' autoload :CacheHelper, 'action_view/helpers/cache_helper' autoload :CaptureHelper, 'action_view/helpers/capture_helper' autoload :DateHelper, 'action_view/helpers/date_helper' @@ -33,10 +34,11 @@ module ActionView #:nodoc: include SanitizeHelper::ClassMethods end + include ActiveSupport::Benchmarkable + include ActiveModelHelper include AssetTagHelper include AtomFeedHelper - include BenchmarkHelper include CacheHelper include CaptureHelper include DateHelper diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 7cc1e48572..c70f29f098 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -191,19 +191,19 @@ module ActionView options = params.extract_options!.symbolize_keys objects = Array.wrap(options.delete(:object) || params).map do |object| - unless object.respond_to?(:to_model) - object = instance_variable_get("@#{object}") - object = convert_to_model(object) - else - object = object.to_model - options[:object_name] ||= object.class.model_name.human + object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model) + object = convert_to_model(object) + + if object.class.respond_to?(:model_name) + options[:object_name] ||= object.class.model_name.human.downcase end + object end objects.compact! - count = objects.inject(0) {|sum, object| sum + object.errors.count } + unless count.zero? html = {} [:id, :class].each do |key| @@ -216,16 +216,20 @@ module ActionView end options[:object_name] ||= params.first - I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale| + I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale| header_message = if options.include?(:header_message) options[:header_message] else - object_name = options[:object_name].to_s.gsub('_', ' ') - object_name = I18n.t(options[:object_name].to_s, :default => object_name, :scope => [:activerecord, :models], :count => 1) - locale.t :header, :count => count, :model => object_name + locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ') end + message = options.include?(:message) ? options[:message] : locale.t(:body) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join + + error_messages = objects.sum do |object| + object.errors.full_messages.map do |msg| + content_tag(:li, ERB::Util.html_escape(msg)) + end + end.join contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb deleted file mode 100644 index c13a069a35..0000000000 --- a/actionpack/lib/action_view/helpers/benchmark_helper.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'active_support/core_ext/benchmark' - -module ActionView - module Helpers - # This helper offers a method to measure the execution time of a block - # in a template. - module BenchmarkHelper - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. - # - # <% benchmark "Process data files" do %> - # <%= expensive_files_operation %> - # <% end %> - # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. - # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. - # - # <% benchmark "Low-level files", :level => :debug do %> - # <%= lowlevel_files_operation %> - # <% end %> - # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: - # - # <% benchmark "Process data files", :level => :info, :silence => true do %> - # <%= expensive_and_chatty_files_operation %> - # <% end %> - def benchmark(message = "Benchmarking", options = {}) - if controller.logger - if options.is_a?(Symbol) - ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller) - options = { :level => options, :silence => false } - else - options.assert_valid_keys(:level, :silence) - options[:level] ||= :info - end - - result = nil - ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield } - controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) - result - else - yield - end - end - end - end -end diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 84d94fd700..5e2a92b89a 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -102,7 +102,7 @@ minute: "Minute" second: "Seconds" - activerecord: + activemodel: errors: template: header: @@ -114,4 +114,4 @@ support: select: # default value for :prompt => true in FormOptionsHelper - prompt: "Please select"
\ No newline at end of file + prompt: "Please select" diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index b6f5b9b6d1..fc89726670 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -123,7 +123,6 @@ module ActionView template.render(self, locals) end - @cached_content_for_layout = content @_content_for[:layout] = content if layout @@ -134,4 +133,4 @@ module ActionView content end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 8993dd2b35..88aeb4b053 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -42,9 +42,11 @@ module ActionView self.erubis_implementation = Erubis def compile(template) - magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/ - erb = "#{magic}<% __in_erb_template=true %>#{template.source}" - self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '') + erb = "<% __in_erb_template=true %>#{source}" + result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + result = "#{$2}\n#{result}" if $2 + result end end end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index f5591ead09..7336114e1b 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -121,7 +121,7 @@ module ActionView # # :api: plugin def path_to_details(path) # [:erb, :format => :html, :locale => :en, :partial => true/false] - if m = path.match(%r'(?:^|/)(_)?[\w-]+(\.[\w-]+)*\.(\w+)$') + if m = path.match(%r'(?:^|/)(_)?[\w-]+((?:\.[\w-]+)*)\.(\w+)$') partial = m[1] == '_' details = (m[2]||"").split('.').reject { |e| e.empty? } handler = Template.handler_class_for_extension(m[3]) diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index 0f64c23649..d1970ca3c7 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -27,10 +27,10 @@ module ActionView end def render(view, locals, &block) - ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do + ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do method_name = compile(locals, view) view.send(method_name, locals, &block) - end.result + end rescue Exception => e if e.is_a?(TemplateError) e.sub_template_of(self) diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index f7d0df5ba0..f6e011a5ab 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,6 +1,8 @@ module ActionView #:nodoc: class TextTemplate < String #:nodoc: - def initialize(string, content_type = Mime[:html]) + HTML = Mime[:html] + + def initialize(string, content_type = HTML) super(string.to_s) @content_type = Mime[content_type] || content_type end @@ -14,11 +16,11 @@ module ActionView #:nodoc: end def inspect - 'inline template' + 'text template' end def render(*args) - self + to_s end def mime_type diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index 5a363c9aa5..6c28f01fa4 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -1,17 +1,17 @@ require 'abstract_unit' +ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers' + module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base include AbstractController::RenderingController include Helpers - - def render(string) - super(:_template_name => string) + + def with_module + render :inline => "Module <%= included_method %>" end - - append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) end module HelperyTest @@ -20,24 +20,61 @@ module AbstractController end end - class MyHelpers1 < ControllerWithHelpers + class AbstractHelpers < ControllerWithHelpers helper(HelperyTest) do def helpery_test "World" end end - - def index - render "helper_test.erb" + + helper :abc + + def with_block + render :inline => "Hello <%= helpery_test %>" + end + + def with_symbol + render :inline => "I respond to bare_a: <%= respond_to?(:bare_a) %>" + end + end + + class AbstractHelpersBlock < ControllerWithHelpers + helper do + include HelperyTest end end - + class TestHelpers < ActiveSupport::TestCase - def test_helpers - controller = MyHelpers1.new - controller.process(:index) - assert_equal "Hello World : Included", controller.response_body + + def setup + @controller = AbstractHelpers.new end + + def test_helpers_with_block + @controller.process(:with_block) + assert_equal "Hello World", @controller.response_body + end + + def test_helpers_with_module + @controller.process(:with_module) + assert_equal "Module Included", @controller.response_body + end + + def test_helpers_with_symbol + @controller.process(:with_symbol) + assert_equal "I respond to bare_a: true", @controller.response_body + end + + def test_declare_missing_helper + assert_raise(MissingSourceFile) { AbstractHelpers.helper :missing } + end + + def test_helpers_with_module_through_block + @controller = AbstractHelpersBlock.new + @controller.process(:with_module) + assert_equal "Module Included", @controller.response_body + end + end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 453d31826e..9c29696ad5 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -17,17 +17,6 @@ module AbstractControllerTests "layouts/omg.erb" => "OMGHI2U <%= yield %>", "layouts/with_false_layout.erb" => "False Layout <%= yield %>" )] - - def self.controller_path - @controller_path ||= self.name.sub(/Controller$/, '').underscore - end - - def controller_path() self.class.controller_path end - - def render_to_body(options) - options[:_layout] = _default_layout({}) - super - end end class Blank < Base @@ -44,6 +33,22 @@ module AbstractControllerTests def index render :_template => ActionView::TextTemplate.new("Hello string!") end + + def overwrite_default + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => :default + end + + def overwrite_false + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => false + end + + def overwrite_string + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => "omg" + end + + def overwrite_skip + render :text => "Hello text!" + end end class WithStringChild < WithString @@ -153,7 +158,31 @@ module AbstractControllerTests controller.process(:index) assert_equal "With String Hello string!", controller.response_body end - + + test "when layout is overwriten by :default in render, render default layout" do + controller = WithString.new + controller.process(:overwrite_default) + assert_equal "With String Hello string!", controller.response_body + end + + test "when layout is overwriten by string in render, render new layout" do + controller = WithString.new + controller.process(:overwrite_string) + assert_equal "OMGHI2U Hello string!", controller.response_body + end + + test "when layout is overwriten by false in render, render no layout" do + controller = WithString.new + controller.process(:overwrite_false) + assert_equal "Hello string!", controller.response_body + end + + test "when text is rendered, render no layout" do + controller = WithString.new + controller.process(:overwrite_skip) + assert_equal "Hello text!", controller.response_body + end + test "when layout is specified as a string, but the layout is missing, raise an exception" do assert_raises(ActionView::MissingTemplate) { WithMissingLayout.new.process(:index) } end diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb new file mode 100644 index 0000000000..6f9bb693f7 --- /dev/null +++ b/actionpack/test/abstract/localized_cache_test.rb @@ -0,0 +1,57 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class CachedController < AbstractController::Base + include AbstractController::RenderingController + include AbstractController::LocalizedCache + + self.view_paths = [ActionView::FixtureResolver.new( + "default.erb" => "With Default", + "template.erb" => "With Template", + "some/file.erb" => "With File", + "template_name.erb" => "With Template Name" + )] + end + + class TestLocalizedCache < ActiveSupport::TestCase + + def setup + @controller = CachedController.new + CachedController.clear_template_caches! + end + + def test_templates_are_cached + @controller.render :template => "default.erb" + assert_equal "With Default", @controller.response_body + + cached = @controller.class.template_cache + assert_equal 1, cached.size + assert_kind_of ActionView::Template, cached.values.first["default.erb"] + end + + def test_cache_is_used + CachedController.new.render :template => "default.erb" + + @controller.expects(:find_template).never + @controller.render :template => "default.erb" + + assert_equal 1, @controller.class.template_cache.size + end + + def test_cache_changes_with_locale + CachedController.new.render :template => "default.erb" + + I18n.locale = :es + @controller.render :template => "default.erb" + + assert_equal 2, @controller.class.template_cache.size + ensure + I18n.locale = :en + end + + end + + end +end diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb new file mode 100644 index 0000000000..45a4763fe4 --- /dev/null +++ b/actionpack/test/abstract/render_test.rb @@ -0,0 +1,88 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class ControllerRenderer < AbstractController::Base + include AbstractController::RenderingController + + self.view_paths = [ActionView::FixtureResolver.new( + "default.erb" => "With Default", + "template.erb" => "With Template", + "some/file.erb" => "With File", + "template_name.erb" => "With Template Name" + )] + + def template + render :template => "template" + end + + def file + render :file => "some/file" + end + + def inline + render :inline => "With <%= :Inline %>" + end + + def text + render :text => "With Text" + end + + def default + render + end + + def template_name + render :_template_name => :template_name + end + + def object + render :_template => ActionView::TextTemplate.new("With Object") + end + end + + class TestRenderer < ActiveSupport::TestCase + + def setup + @controller = ControllerRenderer.new + end + + def test_render_template + @controller.process(:template) + assert_equal "With Template", @controller.response_body + end + + def test_render_file + @controller.process(:file) + assert_equal "With File", @controller.response_body + end + + def test_render_inline + @controller.process(:inline) + assert_equal "With Inline", @controller.response_body + end + + def test_render_text + @controller.process(:text) + assert_equal "With Text", @controller.response_body + end + + def test_render_default + @controller.process(:default) + assert_equal "With Default", @controller.response_body + end + + def test_render_template_name + @controller.process(:template_name) + assert_equal "With Template Name", @controller.response_body + end + + def test_render_object + @controller.process(:object) + assert_equal "With Object", @controller.response_body + end + + end + end +end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index f7a1564f90..214d79cd87 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,13 +1,12 @@ -bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment" -if File.exist?("#{bundled}.rb") - require bundled -else - $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" - $:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib" +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift "#{root}/activesupport/lib" + $:.unshift "#{root}/activemodel/lib" end $:.unshift(File.dirname(__FILE__) + '/../lib') - $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') @@ -106,6 +105,26 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase self.app = build_app + class StubDispatcher + def self.new(*args) + lambda { |env| + params = env['action_dispatch.request.path_parameters'] + controller, action = params[:controller], params[:action] + [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] + } + end + end + + def self.stub_controllers + old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } + yield ActionDispatch::Routing::RouteSet.new + ensure + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher } + end + def with_routing(&block) real_routes = ActionController::Routing::Routes ActionController::Routing.module_eval { remove_const :Routes } diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 37f1f6dff8..ad744421db 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -98,14 +98,6 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - def test_formatted_url_helper_is_deprecated - with_test_routes do - assert_deprecated do - formatted_polymorphic_url([@project, :pdf]) - end - end - end - def test_format_option with_test_routes do @project.save @@ -251,6 +243,12 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_array_containing_symbols + with_test_routes do + assert_equal "http://example.com/series/new", polymorphic_url([:new, :series]) + end + end + def test_with_hash with_test_routes do @project.save diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index b97ceb4594..b57550a69a 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -10,12 +10,12 @@ module Submodule def public_action render :nothing => true end - + hide_action :hidden_action def hidden_action raise "Noooo!" end - + def another_hidden_action end hide_action :another_hidden_action @@ -30,25 +30,25 @@ class NonEmptyController < ActionController::Base def public_action render :nothing => true end - + hide_action :hidden_action def hidden_action end end class MethodMissingController < ActionController::Base - + hide_action :shouldnt_be_called def shouldnt_be_called raise "NO WAY!" end - + protected - + def method_missing(selector) render :text => selector.to_s end - + end class DefaultUrlOptionsController < ActionController::Base @@ -79,7 +79,7 @@ class ControllerInstanceTests < Test::Unit::TestCase @empty = EmptyController.new @contained = Submodule::ContainedEmptyController.new @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] - + @non_empty_controllers = [NonEmptyController.new, Submodule::ContainedNonEmptyController.new] end @@ -135,24 +135,24 @@ class PerformActionTest < ActionController::TestCase rescue_action_in_public! end - + def test_get_on_priv_should_show_selector use_controller MethodMissingController get :shouldnt_be_called assert_response :success assert_equal 'shouldnt_be_called', @response.body end - + def test_method_missing_is_not_an_action_name use_controller MethodMissingController assert ! @controller.__send__(:action_method?, 'method_missing') - + get :method_missing assert_response :success assert_equal 'method_missing', @response.body end - + def test_get_on_hidden_should_fail use_controller NonEmptyController assert_raise(ActionController::UnknownAction) { get :hidden_action } diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 495b431307..3ce90b6ccf 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -228,12 +228,16 @@ class ActionCachingMockController @mock_url_for end + def params + request.parameters + end + def request mocked_path = @mock_path Object.new.instance_eval(<<-EVAL) def path; '#{@mock_path}' end def format; 'all' end - def cache_format; nil end + def parameters; {:format => nil}; end self EVAL end @@ -466,7 +470,7 @@ class ActionCacheTest < ActionController::TestCase @mock_controller.mock_url_for = 'http://example.org/' @mock_controller.mock_path = '/' - assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {}) + assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path end def test_file_extensions @@ -628,20 +632,13 @@ class FragmentCachingTest < ActionController::TestCase def test_fragment_for_logging fragment_computed = false - - listener = [] - ActiveSupport::Orchestra.register listener + ActiveSupport::Notifications.queue.expects(:publish).times(2) buffer = 'generated till now -> ' @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } - assert_equal 1, listener.count { |e| e.name == :fragment_exist? } - assert_equal 1, listener.count { |e| e.name == :write_fragment } - assert fragment_computed assert_equal 'generated till now -> ', buffer - ensure - ActiveSupport::Orchestra.unregister listener end end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index b429cbf0e6..53d4364576 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -106,7 +106,7 @@ class CookieTest < ActionController::TestCase def test_cookiejar_accessor @request.cookies["user_name"] = "david" @controller.request = @request - jar = ActionController::CookieJar.new(@controller) + jar = ActionController::CookieJar.build(@controller.request, @controller.response) assert_equal "david", jar["user_name"] assert_equal nil, jar["something_else"] end @@ -114,14 +114,14 @@ class CookieTest < ActionController::TestCase def test_cookiejar_accessor_with_array_value @request.cookies["pages"] = %w{1 2 3} @controller.request = @request - jar = ActionController::CookieJar.new(@controller) + jar = ActionController::CookieJar.build(@controller.request, @controller.response) assert_equal %w{1 2 3}, jar["pages"] end def test_cookiejar_delete_removes_item_and_returns_its_value @request.cookies["user_name"] = "david" @controller.response = @response - jar = ActionController::CookieJar.new(@controller) + jar = ActionController::CookieJar.build(@controller.request, @controller.response) assert_equal "david", jar.delete("user_name") end @@ -131,9 +131,8 @@ class CookieTest < ActionController::TestCase end def test_cookies_persist_throughout_request - get :authenticate - cookies = @controller.send(:cookies) - assert_equal 'david', cookies['user_name'] + response = get :authenticate + assert response.headers["Set-Cookie"] =~ /user_name=david/ end private diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index 19232c6bc9..43bef34885 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -19,23 +19,23 @@ class FilterParamTest < ActionController::TestCase def method_missing(method, *args) @logged ||= [] - @logged << args.first + @logged << args.first unless block_given? + @logged << yield if block_given? end end setup :set_logger + def test_filter_parameters_must_have_one_word + assert_raises RuntimeError do + FilterParamController.filter_parameter_logging + end + end + def test_filter_parameters assert FilterParamController.respond_to?(:filter_parameter_logging) - assert !@controller.respond_to?(:filter_parameters) - - FilterParamController.filter_parameter_logging - assert @controller.respond_to?(:filter_parameters) - test_hashes = [[{},{},[]], - [{'foo'=>nil},{'foo'=>nil},[]], - [{'foo'=>'bar'},{'foo'=>'bar'},[]], - [{'foo'=>1},{'foo'=>1},[]], + test_hashes = [ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'], [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'], diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 23149fee27..12539739aa 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -57,40 +57,6 @@ class HelperTest < Test::Unit::TestCase assert_equal [], missing_methods end - def test_declare_helper - require 'abc_helper' - self.test_helper = AbcHelper - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { @controller_class.helper :abc } - assert_equal [], missing_methods - end - - def test_declare_missing_helper - assert_equal expected_helper_methods, missing_methods - assert_raise(MissingSourceFile) { @controller_class.helper :missing } - end - - def test_declare_missing_file_from_helper - require 'broken_helper' - rescue LoadError => e - assert_nil(/\bbroken_helper\b/.match(e.to_s)[1]) - end - - def test_helper_block - assert_nothing_raised { - @controller_class.helper { def block_helper_method; end } - } - assert master_helper_methods.include?('block_helper_method') - end - - def test_helper_block_include - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { - @controller_class.helper { include HelperTest::TestHelper } - } - assert [], missing_methods - end - def test_helper_method assert_nothing_raised { @controller_class.helper_method :delegate_method } assert master_helper_methods.include?('delegate_method') diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index fe95fb5750..d6e2a5a974 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -390,7 +390,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "integration_process_test/integration" + match ':action', :to => IntegrationController end yield end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index a79648396c..b070f925d4 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -760,6 +760,14 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "Resource name is david", @response.body end + def test_using_resource_with_responder + RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } + get :using_resource + assert_equal "Resource name is david", @response.body + ensure + RespondWithController.responder = ActionController::Responder + end + def test_not_acceptable @request.accept = "application/xml" get :using_defaults diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2db524ca4b..ac8dad7c42 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -73,6 +73,11 @@ class TestController < ActionController::Base render :action => 'hello_world' end + def conditional_hello_with_expires_now + expires_now + render :action => 'hello_world' + end + def conditional_hello_with_bangs render :action => 'hello_world' end @@ -1321,6 +1326,11 @@ class ExpiresInRenderTest < ActionController::TestCase 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"] end + + def test_expires_now + get :conditional_hello_with_expires_now + assert_equal "no-cache", @response.headers["Cache-Control"] + end end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 054a9f2aaf..2b1f532b8d 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -343,9 +343,9 @@ class RescueTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect 'foo', :controller => "rescue_test/test", :action => 'foo' - map.connect 'invalid', :controller => "rescue_test/test", :action => 'invalid' - map.connect 'b00m', :controller => "rescue_test/test", :action => 'b00m' + match 'foo', :to => TestController.action(:foo) + match 'invalid', :to => TestController.action(:invalid) + match 'b00m', :to => TestController.action(:b00m) end yield end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 5b47de19ae..04e9acf855 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase end def test_should_arrange_actions - resource = ActionController::Resources::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :collection => { :rss => :get, :reorder => :post, :csv => :post }, :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, :new => { :preview => :get, :draft => :get }) @@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase end def test_should_resource_controller_name_equal_resource_name_by_default - resource = ActionController::Resources::Resource.new(:messages, {}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}) assert_equal 'messages', resource.controller end def test_should_resource_controller_name_equal_controller_option - resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts') + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts') assert_equal 'posts', resource.controller end def test_should_all_singleton_paths_be_the_same [ :path, :nesting_path_prefix, :member_path ].each do |method| - resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin') + resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin') assert_equal 'admin/messages', resource.send(method) end end @@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase end def test_override_paths_for_default_restful_actions - resource = ActionController::Resources::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :path_names => {:new => 'nuevo', :edit => 'editar'}) assert_equal resource.new_path, "#{resource.path}/nuevo" end @@ -135,7 +135,7 @@ class ResourcesTest < ActionController::TestCase def test_with_custom_conditions with_restful_routing :messages, :conditions => { :subdomain => 'app' } do - assert ActionController::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') + assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') end end @@ -281,7 +281,7 @@ class ResourcesTest < ActionController::TestCase def test_with_member_action_and_requirement expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} - + with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) end @@ -701,8 +701,8 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_head_method_for_member_routes with_routing do |set| - set.draw do |map| - assert_raise(ArgumentError) do + assert_raise(ArgumentError) do + set.draw do |map| map.resources :messages, :member => {:something => :head} end end @@ -711,8 +711,8 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_http_methods_for_member_routes with_routing do |set| - set.draw do |map| - assert_raise(ArgumentError) do + assert_raise(ArgumentError) do + set.draw do |map| map.resources :messages, :member => {:something => :invalid} end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 7c88520bac..308e2a85b1 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -11,24 +11,15 @@ end ROUTING = ActionController::Routing -class ROUTING::RouteBuilder - attr_reader :warn_output - - def warn(msg) - (@warn_output ||= []) << msg - end -end - # See RFC 3986, section 3.3 for allowed path characters. class UriReservedCharactersRoutingTest < Test::Unit::TestCase def setup - ActionController::Routing.use_controllers! ['controller'] @set = ActionController::Routing::RouteSet.new @set.draw do |map| map.connect ':controller/:action/:variable/*additional' end - safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ]) + safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase } @segment = "#{safe.join}#{unsafe.join}".freeze @@ -36,8 +27,9 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end def test_route_generation_escapes_unsafe_path_characters - assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", - @set.generate(:controller => "contr#{@segment}oller", + @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "variable", :additional => "foo") + assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", + @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "var#{@segment}iable", :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"]) @@ -52,7 +44,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end def test_route_generation_allows_passing_non_string_values_to_generated_helper - assert_equal "/controller/action/variable/1/2", @set.generate(:controller => "controller", + assert_equal "/content/action/variable/1/2", @set.generate(:controller => "content", :action => "action", :variable => "variable", :additional => [1, 2]) @@ -118,8 +110,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase ActionController::Base.optimise_named_routes = true @rs = ::ActionController::Routing::RouteSet.new - - ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) end def teardown @@ -366,10 +356,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" assert results, "Recognition should have succeeded" assert_equal ['hello world', 'how are you?'], results[:path] - - results = rs.recognize_path "/file" - assert results, "Recognition should have succeeded" - assert_equal [], results[:path] end def test_paths_slashes_unescaped_with_ordered_parameters @@ -379,7 +365,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase # No / to %2F in URI, only for query params. x = setup_for_named_route - assert_equal("/file/hello/world", x.send(:path_path, 'hello/world')) + assert_equal("/file/hello/world", x.send(:path_path, ['hello', 'world'])) end def test_non_controllers_cannot_be_matched @@ -407,35 +393,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase rs.draw do |map| map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} end - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } - assert_match /^post_url failed to generate/, exception.message - from_match = exception.message.match(/from \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, from_match - assert_match /:action=>"show"/, from_match - assert_match /:controller=>"post"/, from_match - - expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s - assert_no_match /:bad_param=>"foo"/, expected_match - assert_match /:action=>"show"/, expected_match - assert_match /:controller=>"post"/, expected_match - - diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, diff_match - assert_no_match /:action=>"show"/, diff_match - assert_no_match /:controller=>"post"/, diff_match - end - - # this specifies the case where your formerly would get a very confusing error message with an empty diff - def test_should_have_better_error_message_when_options_diff_is_empty - rs.draw do |map| - map.content '/content/:query', :controller => 'content', :action => 'show' - end - - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") } - assert_match %r[:action=>"show"], exception.message - assert_match %r[:controller=>"content"], exception.message - assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message - assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message + assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } end def test_dynamic_path_allowed @@ -503,17 +461,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) end - def test_recognition_with_uppercase_controller_name - @rs.draw {|m| m.connect ':controller/:action/:id' } - assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) - assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) - assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) - - # these used to work, before the routes rewrite, but support for this was pulled in the new version... - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) - end - def test_requirement_should_prevent_optional_id rs.draw do |map| map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} @@ -760,12 +707,9 @@ class RouteSetTest < ActiveSupport::TestCase def default_route_set @default_route_set ||= begin - set = nil - ActionController::Routing.with_controllers(['accounts']) do - set = ROUTING::RouteSet.new - set.draw do |map| - map.connect '/:controller/:action/:id/' - end + set = ROUTING::RouteSet.new + set.draw do |map| + map.connect '/:controller/:action/:id/' end set end @@ -953,44 +897,38 @@ class RouteSetTest < ActiveSupport::TestCase end def test_draw_default_route - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id' - end + set.draw do |map| + map.connect '/:controller/:action/:id' + end - assert_equal 1, set.routes.size + assert_equal 1, set.routes.size - assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) - assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) + assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) + assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) - end + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) end def test_draw_default_route_with_default_controller - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id', :controller => 'users' - end - assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) + set.draw do |map| + map.connect '/:controller/:action/:id', :controller => 'users' end + assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) end def test_route_with_parameter_shell - ActionController::Routing.with_controllers(['users', 'pages']) do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - map.connect '/:controller/:action/:id' - end + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ + map.connect '/:controller/:action/:id' + end - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) - assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) + assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) - end + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) end def test_route_requirements_with_anchor_chars_are_invalid @@ -1019,14 +957,6 @@ class RouteSetTest < ActiveSupport::TestCase map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ end end - assert_nothing_raised do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ - end - assert_raise ActionController::RoutingError do - set.generate :controller => 'pages', :action => 'show', :id => 10 - end - end end def test_route_requirements_with_invalid_http_method_is_invalid @@ -1053,19 +983,6 @@ class RouteSetTest < ActiveSupport::TestCase end end - def test_non_path_route_requirements_match_all - set.draw do |map| - map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ - end - assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') - assert_raise ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') - end - assert_raise ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') - end - end - def test_recognize_with_encoded_id_and_regex set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/ @@ -1303,16 +1220,16 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal "/foo/bar/baz/7", url end - def test_id_is_not_impossibly_sticky - set.draw do |map| - map.connect 'foo/:number', :controller => "people", :action => "index" - map.connect ':controller/:action/:id' - end - - url = set.generate({:controller => "people", :action => "index", :number => 3}, - {:controller => "people", :action => "index", :id => "21"}) - assert_equal "/foo/3", url - end + # def test_id_is_not_impossibly_sticky + # set.draw do |map| + # map.connect 'foo/:number', :controller => "people", :action => "index" + # map.connect ':controller/:action/:id' + # end + # + # url = set.generate({:controller => "people", :action => "index", :number => 3}, + # {:controller => "people", :action => "index", :id => "21"}) + # assert_equal "/foo/3", url + # end def test_id_is_sticky_when_it_ought_to_be set.draw do |map| @@ -1375,20 +1292,20 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do |map| map.connect ':controller/:action/:id' end - assert_equal '/post', set.generate( - {:controller => 'post', :action => 'index'}, - {:controller => 'post', :action => 'show', :id => '10'} + assert_equal '/books', set.generate( + {:controller => 'books', :action => 'index'}, + {:controller => 'books', :action => 'show', :id => '10'} ) end def test_query_params_will_be_shown_when_recalled set.draw do |map| - map.connect 'show_post/:parameter', :controller => 'post', :action => 'show' + map.connect 'show_weblog/:parameter', :controller => 'weblog', :action => 'show' map.connect ':controller/:action/:id' end - assert_equal '/post/edit?parameter=1', set.generate( + assert_equal '/weblog/edit?parameter=1', set.generate( {:action => 'edit', :parameter => 1}, - {:controller => 'post', :action => 'show', :parameter => 1} + {:controller => 'weblog', :action => 'show', :parameter => 1} ) end @@ -1410,23 +1327,9 @@ class RouteSetTest < ActiveSupport::TestCase def test_expiry_determination_should_consider_values_with_to_param set.draw { |map| map.connect 'projects/:project_id/:controller/:action' } - assert_equal '/projects/1/post/show', set.generate( + assert_equal '/projects/1/weblog/show', set.generate( {:action => 'show', :project_id => 1}, - {:controller => 'post', :action => 'show', :project_id => '1'}) - end - - def test_generate_all - set.draw do |map| - map.connect 'show_post/:id', :controller => 'post', :action => 'show' - map.connect ':controller/:action/:id' - end - all = set.generate( - {:action => 'show', :id => 10, :generate_all => true}, - {:controller => 'post', :action => 'show'} - ) - assert_equal 2, all.length - assert_equal '/show_post/10', all.first - assert_equal '/post/show/10', all.last + {:controller => 'weblog', :action => 'show', :project_id => '1'}) end def test_named_route_in_nested_resource @@ -1603,101 +1506,53 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named')) end - - def test_interpolation_chunk_should_respect_raw - ActionController::Routing.with_controllers(['hello']) do - set.draw do |map| - map.connect '/Hello World', :controller => 'hello' - end - - assert_equal '/Hello%20World', set.generate(:controller => 'hello') - assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World')) - assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') } - end - end - - def test_value_should_not_be_double_unescaped - ActionController::Routing.with_controllers(['foo']) do - set.draw do |map| - map.connect '/Карта', :controller => 'foo' - end - - assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo') - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта')) - assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') } - end - end - - def test_regexp_chunk_should_escape_specials - ActionController::Routing.with_controllers(['foo', 'bar']) do - set.draw do |map| - map.connect '/Hello*World', :controller => 'foo' - map.connect '/HelloWorld', :controller => 'bar' - end - - assert_equal '/Hello*World', set.generate(:controller => 'foo') - assert_equal '/HelloWorld', set.generate(:controller => 'bar') - - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World')) - assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld')) - end - end - def test_regexp_chunk_should_add_question_mark_for_optionals - ActionController::Routing.with_controllers(['foo', 'bar']) do - set.draw do |map| - map.connect '/', :controller => 'foo' - map.connect '/hello', :controller => 'bar' - end + set.draw do |map| + map.connect '/', :controller => 'foo' + map.connect '/hello', :controller => 'bar' + end - assert_equal '/', set.generate(:controller => 'foo') - assert_equal '/hello', set.generate(:controller => 'bar') + assert_equal '/', set.generate(:controller => 'foo') + assert_equal '/hello', set.generate(:controller => 'bar') - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/')) - assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello')) - end + assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/')) + assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello')) end def test_assign_route_options_with_anchor_chars - ActionController::Routing.with_controllers(['cars']) do - set.draw do |map| - map.connect '/cars/:action/:person/:car/', :controller => 'cars' - end + set.draw do |map| + map.connect '/cars/:action/:person/:car/', :controller => 'cars' + end - assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2') + assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2') - assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2')) - end + assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2')) end def test_segmentation_of_dot_path - ActionController::Routing.with_controllers(['books']) do - set.draw do |map| - map.connect '/books/:action.rss', :controller => 'books' - end + set.draw do |map| + map.connect '/books/:action.rss', :controller => 'books' + end - assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list') + assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list') - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss')) - end + assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss')) end def test_segmentation_of_dynamic_dot_path - ActionController::Routing.with_controllers(['books']) do - set.draw do |map| - map.connect '/books/:action.:format', :controller => 'books' - end + set.draw do |map| + map.connect '/books/:action.:format', :controller => 'books' + end - assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss') - assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml') - assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list') - assert_equal '/books', set.generate(:controller => 'books', :action => 'index') + assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss') + assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml') + assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list') + assert_equal '/books', set.generate(:controller => 'books', :action => 'index') - assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss')) - assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml')) - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list')) - assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books')) - end + assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss')) + assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml')) + assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list')) + assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books')) end def test_slashes_are_implied @@ -1752,7 +1607,6 @@ class RouteSetTest < ActiveSupport::TestCase def test_default_route_should_uri_escape_pluses expected = { :controller => 'pages', :action => 'show', :id => 'hello world' } - assert_equal expected, default_route_set.recognize_path('/pages/show/hello world') assert_equal expected, default_route_set.recognize_path('/pages/show/hello%20world') assert_equal '/pages/show/hello%20world', default_route_set.generate(expected, expected) @@ -1762,52 +1616,44 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal '/pages/show/hello+world', default_route_set.generate(expected, expected) end - def test_parameter_shell - page_url = ROUTING::Route.new - page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} - assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) - end - - def test_defaults - route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" - assert_equal( - { :controller => "users", :action => "show", :format => "html" }, - route.defaults) - end - - def test_builder_complains_without_controller - assert_raise(ArgumentError) do - ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" - end - end - def test_build_empty_query_string - assert_equal '/foo', default_route_set.generate({:controller => 'foo'}) + assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo'}) end def test_build_query_string_with_nil_value - assert_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil}) + assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil}) end def test_simple_build_query_string - assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'}) + assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'}) end def test_convert_ints_build_query_string - assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2}) + assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2}) end def test_escape_spaces_build_query_string - assert_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'}) + assert_uri_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'}) end def test_expand_array_build_query_string - assert_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) + assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) end def test_escape_spaces_build_query_string_selected_keys - assert_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'}) + assert_uri_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'}) end + + private + def assert_uri_equal(expected, actual) + assert_equal(sort_query_string_params(expected), sort_query_string_params(actual)) + end + + def sort_query_string_params(uri) + path, qs = uri.split('?') + qs = qs.split('&').sort.join('&') if qs + qs ? "#{path}?#{qs}" : path + end end class RouteLoadingTest < Test::Unit::TestCase @@ -1888,3 +1734,315 @@ class RouteLoadingTest < Test::Unit::TestCase ActionController::Routing::Routes end end + +class RackMountIntegrationTests < ActiveSupport::TestCase + Model = Struct.new(:to_param) + + Mapping = lambda { |map| + map.namespace :admin do |admin| + admin.resources :users + end + + map.namespace 'api' do |api| + api.root :controller => 'users' + end + + map.connect 'blog/:year/:month/:day', + :controller => 'posts', + :action => 'show_date', + :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/}, + :day => nil, + :month => nil + + map.blog('archive/:year', :controller => 'archive', :action => 'index', + :defaults => { :year => nil }, + :requirements => { :year => /\d{4}/ } + ) + + map.resources :people + map.connect 'legacy/people', :controller => 'people', :action => 'index', :legacy => 'true' + + map.connect 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol + map.connect 'id_default/:id', :controller => 'foo', :action => 'id_default', :id => 1 + map.connect 'get_or_post', :controller => 'foo', :action => 'get_or_post', :conditions => { :method => [:get, :post] } + map.connect 'optional/:optional', :controller => 'posts', :action => 'index' + map.project 'projects/:project_id', :controller => 'project' + map.connect 'clients', :controller => 'projects', :action => 'index' + + map.connect 'ignorecase/geocode/:postalcode', :controller => 'geocode', + :action => 'show', :postalcode => /hx\d\d-\d[a-z]{2}/i + map.geocode 'extended/geocode/:postalcode', :controller => 'geocode', + :action => 'show',:requirements => { + :postalcode => /# Postcode format + \d{5} #Prefix + (-\d{4})? #Suffix + /x + } + + map.connect '', :controller => 'news', :format => nil + map.connect 'news.:format', :controller => 'news' + + map.connect 'comment/:id/:action', :controller => 'comments', :action => 'show' + map.connect 'ws/:controller/:action/:id', :ws => true + map.connect 'account/:action', :controller => :account, :action => :subscription + map.connect 'pages/:page_id/:controller/:action/:id' + map.connect ':controller/ping', :action => 'ping' + map.connect ':controller/:action/:id' + } + + def setup + @routes = ActionController::Routing::RouteSet.new + @routes.draw(&Mapping) + end + + def test_add_route + @routes.clear! + + assert_raise(ActionController::RoutingError) do + @routes.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) + map.connect ':controller/:action/:id' + end + end + end + + def test_recognize_path + assert_equal({:controller => 'admin/users', :action => 'index'}, @routes.recognize_path('/admin/users', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'create'}, @routes.recognize_path('/admin/users', :method => :post)) + assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put)) + assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete)) + assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get)) + + assert_equal({:controller => 'admin/posts', :action => 'index'}, @routes.recognize_path('/admin/posts', :method => :get)) + assert_equal({:controller => 'admin/posts', :action => 'new'}, @routes.recognize_path('/admin/posts/new', :method => :get)) + + assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api', :method => :get)) + assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api/', :method => :get)) + + 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)) + assert_equal({:controller => 'people', :action => 'create'}, @routes.recognize_path('/people', :method => :post)) + assert_equal({:controller => 'people', :action => 'new'}, @routes.recognize_path('/people/new', :method => :get)) + assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get)) + assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get)) + assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put)) + assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete)) + assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get)) + assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get)) + + assert_equal({:controller => 'symbols', :action => 'show', :name => :as_symbol}, @routes.recognize_path('/symbols')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default/1')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '2'}, @routes.recognize_path('/id_default/2')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default')) + assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get)) + assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post)) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) } + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) } + + assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar')) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') } + + assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get)) + assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index', :ws => true}, @routes.recognize_path('/ws/posts', :method => :get)) + + assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account', :method => :get)) + assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account/subscription', :method => :get)) + assert_equal({:controller => 'account', :action => 'billing'}, @routes.recognize_path('/account/billing', :method => :get)) + + assert_equal({:page_id => '1', :controller => 'notes', :action => 'index'}, @routes.recognize_path('/pages/1/notes', :method => :get)) + assert_equal({:page_id => '1', :controller => 'notes', :action => 'list'}, @routes.recognize_path('/pages/1/notes/list', :method => :get)) + assert_equal({:page_id => '1', :controller => 'notes', :action => 'show', :id => '2'}, @routes.recognize_path('/pages/1/notes/show/2', :method => :get)) + + assert_equal({:controller => 'posts', :action => 'ping'}, @routes.recognize_path('/posts/ping', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts/index', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show'}, @routes.recognize_path('/posts/show', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show', :id => '1'}, @routes.recognize_path('/posts/show/1', :method => :get)) + assert_equal({:controller => 'posts', :action => 'create'}, @routes.recognize_path('/posts/create', :method => :post)) + + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1az'}, @routes.recognize_path('/ignorecase/geocode/hx12-1az')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1AZ'}, @routes.recognize_path('/ignorecase/geocode/hx12-1AZ')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345-1234'}, @routes.recognize_path('/extended/geocode/12345-1234')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345'}, @routes.recognize_path('/extended/geocode/12345')) + + assert_equal({:controller => 'news', :action => 'index', :format => nil}, @routes.recognize_path('/', :method => :get)) + assert_equal({:controller => 'news', :action => 'index', :format => 'rss'}, @routes.recognize_path('/news.rss', :method => :get)) + + assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) } + end + + def test_generate + assert_equal '/admin/users', @routes.generate(:use_route => 'admin_users') + assert_equal '/admin/users', @routes.generate(:controller => 'admin/users') + assert_equal '/admin/users', @routes.generate(:controller => 'admin/users', :action => 'index') + assert_equal '/admin/users', @routes.generate({:action => 'index'}, {:controller => 'admin/users'}) + assert_equal '/admin/users', @routes.generate({:controller => 'users', :action => 'index'}, {:controller => 'admin/accounts'}) + assert_equal '/people', @routes.generate({:controller => '/people', :action => 'index'}, {:controller => 'admin/accounts'}) + + assert_equal '/admin/posts', @routes.generate({:controller => 'admin/posts'}) + assert_equal '/admin/posts/new', @routes.generate({:controller => 'admin/posts', :action => 'new'}) + + assert_equal '/blog/2009', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009) + assert_equal '/blog/2009/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1) + assert_equal '/blog/2009/1/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1) + + assert_equal '/archive/2010', @routes.generate(:controller => 'archive', :action => 'index', :year => '2010') + assert_equal '/archive', @routes.generate(:controller => 'archive', :action => 'index') + assert_equal '/archive?year=january', @routes.generate(:controller => 'archive', :action => 'index', :year => 'january') + + assert_equal '/people', @routes.generate(:use_route => 'people') + assert_equal '/people', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index') + assert_equal '/people.xml', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index', :format => 'xml') + assert_equal '/people', @routes.generate({:use_route => 'people', :controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index'}) + assert_equal '/people', @routes.generate(:controller => 'people') + assert_equal '/people', @routes.generate(:controller => 'people', :action => 'index') + assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people'}) + assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people', @routes.generate({}, {:controller => 'people', :action => 'index'}) + assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/new', @routes.generate(:use_route => 'new_person') + assert_equal '/people/new', @routes.generate(:controller => 'people', :action => 'new') + assert_equal '/people/1', @routes.generate(:use_route => 'person', :id => '1') + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => '1') + assert_equal '/people/1.xml', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :format => 'xml') + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => 1) + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => Model.new('1')) + assert_equal '/people/1', @routes.generate({:action => 'show', :id => '1'}, {:controller => 'people', :action => 'index'}) + assert_equal '/people/1', @routes.generate({:action => 'show', :id => 1}, {:controller => 'people', :action => 'show', :id => '1'}) + # assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index', :id => '1'}) + assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/1', @routes.generate({}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'index', :id => '1'}) + assert_equal '/people/1/edit', @routes.generate(:controller => 'people', :action => 'edit', :id => '1') + assert_equal '/people/1/edit.xml', @routes.generate(:controller => 'people', :action => 'edit', :id => '1', :format => 'xml') + assert_equal '/people/1/edit', @routes.generate(:use_route => 'edit_person', :id => '1') + assert_equal '/people/1?legacy=true', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :legacy => 'true') + assert_equal '/people?legacy=true', @routes.generate(:controller => 'people', :action => 'index', :legacy => 'true') + + assert_equal '/id_default/2', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '2') + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '1') + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => 1) + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default') + assert_equal '/optional/bar', @routes.generate(:controller => 'posts', :action => 'index', :optional => 'bar') + assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index') + + assert_equal '/project', @routes.generate({:controller => 'project', :action => 'index'}) + assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index', :project_id => '1'}) + assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index'}, {:project_id => '1'}) + assert_raise(ActionController::RoutingError) { @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}) } + assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1'}) + assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}, {:project_id => '1'}) + + assert_equal '/clients', @routes.generate(:controller => 'projects', :action => 'index') + assert_equal '/clients?project_id=1', @routes.generate(:controller => 'projects', :action => 'index', :project_id => '1') + assert_equal '/clients', @routes.generate({:controller => 'projects', :action => 'index'}, {:project_id => '1'}) + assert_equal '/clients', @routes.generate({:action => 'index'}, {:controller => 'projects', :action => 'index', :project_id => '1'}) + + assert_equal '/comment/20', @routes.generate({:id => 20}, {:controller => 'comments', :action => 'show'}) + assert_equal '/comment/20', @routes.generate(:controller => 'comments', :id => 20, :action => 'show') + assert_equal '/comments/boo', @routes.generate(:controller => 'comments', :action => 'boo') + + assert_equal '/ws/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1', :ws => true) + assert_equal '/ws/posts', @routes.generate(:controller => 'posts', :action => 'index', :ws => true) + + assert_equal '/account', @routes.generate(:controller => 'account', :action => 'subscription') + assert_equal '/account/billing', @routes.generate(:controller => 'account', :action => 'billing') + + assert_equal '/pages/1/notes/show/1', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'show', :id => '1') + assert_equal '/pages/1/notes/list', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'list') + assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'index') + assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes') + assert_equal '/notes', @routes.generate(:page_id => nil, :controller => 'notes') + assert_equal '/notes', @routes.generate(:controller => 'notes') + assert_equal '/notes/print', @routes.generate(:controller => 'notes', :action => 'print') + assert_equal '/notes/print', @routes.generate({}, {:controller => 'notes', :action => 'print'}) + + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1', :foo => 'bar'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:action => 'index'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/show/1', @routes.generate({}, {:controller => 'notes', :action => 'show', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes', :id => '1'}, {:foo => 'bar'}) + assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'notes', :action => 'show', :id => '1'}) + assert_equal '/notes/list', @routes.generate({:action => 'list'}, {:controller => 'notes', :action => 'show', :id => '1'}) + + assert_equal '/posts/ping', @routes.generate(:controller => 'posts', :action => 'ping') + assert_equal '/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1') + assert_equal '/posts', @routes.generate(:controller => 'posts') + assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index') + 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?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 '/', @routes.generate(:controller => 'news', :action => 'index') + assert_equal '/', @routes.generate(:controller => 'news', :action => 'index', :format => nil) + assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss') + + + assert_raise(ActionController::RoutingError) { @routes.generate({:action => 'index'}) } + end + + def test_generate_extras + assert_equal ['/people', []], @routes.generate_extras(:controller => 'people') + assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar') + assert_equal ['/people', []], @routes.generate_extras(:controller => 'people', :action => 'index') + assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'index', :foo => 'bar') + assert_equal ['/people/new', []], @routes.generate_extras(:controller => 'people', :action => 'new') + assert_equal ['/people/new', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'new', :foo => 'bar') + assert_equal ['/people/1', []], @routes.generate_extras(:controller => 'people', :action => 'show', :id => '1') + assert_equal ['/people/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'people', :action => 'show', :id => '1', :foo => '2', :bar => '3')) + assert_equal ['/people', [:person]], @routes.generate_extras(:controller => 'people', :action => 'create', :person => { :first_name => 'Josh', :last_name => 'Peek' }) + assert_equal ['/people', [:people]], @routes.generate_extras(:controller => 'people', :action => 'create', :people => ['Josh', 'Dave']) + + assert_equal ['/posts/show/1', []], @routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1') + assert_equal ['/posts/show/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1', :foo => '2', :bar => '3')) + assert_equal ['/posts', []], @routes.generate_extras(:controller => 'posts', :action => 'index') + assert_equal ['/posts', [:foo]], @routes.generate_extras(:controller => 'posts', :action => 'index', :foo => 'bar') + end + + def test_extras + params = {:controller => 'people'} + assert_equal [], @routes.extra_keys(params) + assert_equal({:controller => 'people'}, params) + + params = {:controller => 'people', :foo => 'bar'} + assert_equal [:foo], @routes.extra_keys(params) + assert_equal({:controller => 'people', :foo => 'bar'}, params) + + params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}} + assert_equal [:person], @routes.extra_keys(params) + assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params) + end + + private + def sort_extras!(extras) + if extras.length == 2 + extras[1].sort! { |a, b| a.to_s <=> b.to_s } + end + extras + end + + def assert_raise(e) + result = yield + flunk "Did not raise #{e}, but returned #{result.inspect}" + rescue e + assert true + end +end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 4c4bf9ade4..3b14cbb2d8 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -224,9 +224,8 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes with_routing do |set| set.draw do |map| - map.no_args '/this/is/verbose', :controller => 'home', :action => 'index' - map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' - map.connect ':controller/:action/:id' + match 'this/is/verbose', :to => 'home#index', :as => :no_args + match 'home/sweet/home/:user', :to => 'home#index', :as => :home end # We need to create a new class in order to install the new named route. @@ -264,7 +263,7 @@ class UrlWriterTests < ActionController::TestCase def test_only_path 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 map.connect ':controller/:action/:id' end @@ -321,8 +320,8 @@ class UrlWriterTests < ActionController::TestCase params = extract_params(url) assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position][]' => 'prof' }.to_query - assert_equal params[3], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query end def test_path_generation_for_symbol_parameter_keys @@ -334,7 +333,6 @@ class UrlWriterTests < ActionController::TestCase set.draw do |map| map.main '', :controller => 'posts', :format => nil map.resources :posts - map.connect ':controller/:action/:id' end # We need to create a new class in order to install the new named route. @@ -359,10 +357,10 @@ class UrlWriterTests < ActionController::TestCase controller = kls.new params = {:id => 1, :format => :xml} assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) end assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) end end end @@ -382,6 +380,6 @@ class UrlWriterTests < ActionController::TestCase private def extract_params(url) - url.split('?', 2).last.split('&') + url.split('?', 2).last.split('&').sort end end diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb index 1a9eb65f29..63e8cf3e61 100644 --- a/actionpack/test/controller/verification_test.rb +++ b/actionpack/test/controller/verification_test.rb @@ -125,8 +125,8 @@ class VerificationTest < ActionController::TestCase assert_not_deprecated do with_routing do |set| set.draw do |map| - map.foo '/foo', :controller => 'test', :action => 'foo' - map.connect ":controller/:action/:id" + match 'foo', :to => 'test#foo', :as => :foo + match 'verification_test/:action', :to => TestController end get :guarded_one_for_named_route_test, :two => "not one" assert_redirected_to '/foo' diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index db6cf7b330..3c2408de5f 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -57,7 +57,7 @@ class JsonParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "json_params_parsing_test/test" + match ':action', :to => TestController end yield end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index a31e326ddf..b764478d87 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -109,7 +109,7 @@ class QueryStringParsingTest < ActionController::IntegrationTest def assert_parses(expected, actual) with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "query_string_parsing_test/test" + match ':action', :to => TestController end get "/parse", actual diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 7167cdafac..e98a49980e 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "url_encoded_params_parsing_test/test" + match ':action', :to => TestController end yield end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index 521002b519..0dc47ed9d5 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -84,7 +84,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "xml_params_parsing_test/test" + match ':action', :to => TestController end yield end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb new file mode 100644 index 0000000000..74e6c8e72d --- /dev/null +++ b/actionpack/test/dispatch/routing_test.rb @@ -0,0 +1,378 @@ +require 'abstract_unit' +require 'controller/fake_controllers' + +class TestRoutingMapper < ActionDispatch::IntegrationTest + SprocketsApp = lambda { |env| + [200, {"Content-Type" => "text/html"}, ["javascripts"]] + } + + class IpRestrictor + def self.matches?(request) + request.ip =~ /192\.168\.1\.1\d\d/ + end + end + + stub_controllers do |routes| + Routes = routes + Routes.draw do |map| + controller :sessions do + get 'login', :to => :new, :as => :login + post 'login', :to => :create + + delete 'logout', :to => :destroy, :as => :logout + end + + match 'account/login', :to => redirect("/login") + + match 'openid/login', :via => [:get, :post], :to => "openid#login" + + controller(:global) do + match 'global/:action' + match 'global/export', :to => :export, :as => :export_request + match 'global/hide_notice', :to => :hide_notice, :as => :hide_notice + match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ } + end + + constraints(:ip => /192\.168\.1\.\d\d\d/) do + get 'admin', :to => "queenbee#index" + end + + constraints IpRestrictor do + get 'admin/accounts', :to => "queenbee#accounts" + end + + resources :projects, :controller => :project do + resources :involvements, :attachments + + resources :participants do + put :update_all, :on => :collection + end + + resources :companies do + resources :people + resource :avatar + end + + resources :images do + post :revise, :on => :member + end + + resources :people do + namespace ":access_token" do + resource :avatar + end + + member do + put :accessible_projects + post :resend, :generate_new_password + end + end + + resources :posts do + get :archive, :toggle_view, :on => :collection + post :preview, :on => :member + + resource :subscription + + resources :comments do + post :preview, :on => :collection + end + end + end + + match 'sprockets.js', :to => 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 + + # misc + match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article + + namespace :account do + resource :subscription, :credit, :credit_card + end + + controller :articles do + scope 'articles' do + scope ':title', :title => /[a-z]+/, :as => :with_title do + match ':id', :to => :with_id + end + end + end + + scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do + resources :rooms + end + end + end + + def app + Routes + end + + def test_logout + with_test_routes do + delete '/logout' + assert_equal 'sessions#destroy', @response.body + + assert_equal '/logout', logout_path + assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true) + end + end + + def test_login + with_test_routes do + get '/login' + assert_equal 'sessions#new', @response.body + assert_equal '/login', login_path + + post '/login' + assert_equal 'sessions#create', @response.body + + assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) + assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + end + end + + def test_login_redirect + with_test_routes do + get '/account/login' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/login', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + + def test_openid + with_test_routes do + get '/openid/login' + assert_equal 'openid#login', @response.body + + post '/openid/login' + assert_equal 'openid#login', @response.body + end + end + + # TODO: rackmount is broken + # def test_admin + # with_test_routes do + # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} + # assert_equal 'queenbee#index', @response.body + # + # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + # + # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} + # assert_equal 'queenbee#accounts', @response.body + # + # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + # end + # end + + def test_global + with_test_routes do + get '/global/dashboard' + assert_equal 'global#dashboard', @response.body + + get '/global/export' + assert_equal 'global#export', @response.body + + get '/global/hide_notice' + assert_equal 'global#hide_notice', @response.body + + get '/export/123/foo.txt' + assert_equal 'global#export', @response.body + + assert_equal '/global/export', export_request_path + assert_equal '/global/hide_notice', hide_notice_path + assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt') + end + end + + def test_projects + with_test_routes do + get '/projects/1' + assert_equal 'projects#show', @response.body + end + end + + def test_projects_involvements + with_test_routes do + get '/projects/1/involvements' + assert_equal 'involvements#index', @response.body + + get '/projects/1/involvements/1' + assert_equal 'involvements#show', @response.body + end + end + + def test_projects_attachments + with_test_routes do + get '/projects/1/attachments' + assert_equal 'attachments#index', @response.body + end + end + + def test_projects_participants + with_test_routes do + get '/projects/1/participants' + assert_equal 'participants#index', @response.body + + put '/projects/1/participants/update_all' + assert_equal 'participants#update_all', @response.body + end + end + + def test_projects_companies + with_test_routes do + get '/projects/1/companies' + assert_equal 'companies#index', @response.body + + get '/projects/1/companies/1/people' + assert_equal 'people#index', @response.body + + get '/projects/1/companies/1/avatar' + assert_equal 'avatar#show', @response.body + end + end + + def test_project_images + with_test_routes do + get '/projects/1/images' + assert_equal 'images#index', @response.body + + post '/projects/1/images/1/revise' + assert_equal 'images#revise', @response.body + end + end + + def test_projects_people + with_test_routes do + get '/projects/1/people' + assert_equal 'people#index', @response.body + + get '/projects/1/people/1' + assert_equal 'people#show', @response.body + + get '/projects/1/people/1/7a2dec8/avatar' + assert_equal 'avatar#show', @response.body + + put '/projects/1/people/1/accessible_projects' + assert_equal 'people#accessible_projects', @response.body + + post '/projects/1/people/1/resend' + assert_equal 'people#resend', @response.body + + post '/projects/1/people/1/generate_new_password' + assert_equal 'people#generate_new_password', @response.body + end + end + + def test_projects_posts + with_test_routes do + get '/projects/1/posts' + assert_equal 'posts#index', @response.body + + get '/projects/1/posts/archive' + assert_equal 'posts#archive', @response.body + + get '/projects/1/posts/toggle_view' + assert_equal 'posts#toggle_view', @response.body + + post '/projects/1/posts/1/preview' + assert_equal 'posts#preview', @response.body + + get '/projects/1/posts/1/subscription' + assert_equal 'subscription#show', @response.body + + get '/projects/1/posts/1/comments' + assert_equal 'comments#index', @response.body + + post '/projects/1/posts/1/comments/preview' + assert_equal 'comments#preview', @response.body + end + end + + def test_sprockets + with_test_routes do + get '/sprockets.js' + assert_equal 'javascripts', @response.body + end + end + + def test_update_person_route + with_test_routes do + get '/people/1/update' + assert_equal 'people#update', @response.body + + assert_equal '/people/1/update', update_person_path(:id => 1) + end + end + + def test_update_project_person + with_test_routes do + get '/projects/1/people/2/update' + assert_equal 'people#update', @response.body + + assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2) + end + end + + def test_articles_perma + with_test_routes do + get '/articles/2009/08/18/rails-3' + assert_equal 'articles#show', @response.body + + assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3') + end + end + + def test_account_namespace + with_test_routes do + get '/account/subscription' + assert_equal 'subscription#show', @response.body + + get '/account/credit' + assert_equal 'credit#show', @response.body + + get '/account/credit_card' + assert_equal 'credit_card#show', @response.body + end + end + + def test_articles_with_id + with_test_routes do + get '/articles/rails/1' + assert_equal 'articles#with_id', @response.body + + assert_raise(ActionController::RoutingError) { get '/articles/123/1' } + + assert_equal '/articles/rails/1', with_title_path(:title => 'rails', :id => 1) + end + end + + def test_access_token_rooms + with_test_routes do + get '/12345/rooms' + assert_equal 'rooms#index', @response.body + + get '/12345/rooms/1' + assert_equal 'rooms#show', @response.body + + get '/12345/rooms/1/edit' + assert_equal 'rooms#edit', @response.body + end + end + + private + def with_test_routes + real_routes, temp_routes = ActionController::Routing::Routes, Routes + + ActionController::Routing.module_eval { remove_const :Routes } + ActionController::Routing.module_eval { const_set :Routes, temp_routes } + + yield + ensure + ActionController::Routing.module_eval { remove_const :Routes } + ActionController::Routing.const_set(:Routes, real_routes) + end +end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index ab5fabde65..edfc303d3d 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -219,7 +219,7 @@ class CookieStoreTest < ActionController::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "cookie_store_test/test" + match ':action', :to => TestController end options = {:key => SessionKey, :secret => SessionSecret}.merge(options) @app = ActionDispatch::Session::CookieStore.new(set, options) diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index c7435bd06b..afc9d91d50 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -112,7 +112,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "mem_cache_store_test/test" + match ':action', :to => TestController end @app = ActionDispatch::Session::MemCacheStore.new(set, :key => '_session_id') yield diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb index c993836a61..250327e6dc 100644 --- a/actionpack/test/lib/controller/fake_controllers.rb +++ b/actionpack/test/lib/controller/fake_controllers.rb @@ -5,9 +5,10 @@ class NotAController; end module Admin class << self; alias_method :const_available?, :const_defined?; end - class UserController < ActionController::Base; end class NewsFeedController < ActionController::Base; end + class PostsController < ActionController::Base; end class StuffController < ActionController::Base; end + class UserController < ActionController::Base; end end module Api @@ -25,7 +26,9 @@ class ElsewhereController < ActionController::Base; end class FooController < ActionController::Base; end class HiController < ActionController::Base; end class ImageController < 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 diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb new file mode 100644 index 0000000000..2465444fc5 --- /dev/null +++ b/actionpack/test/template/active_model_helper_i18n_test.rb @@ -0,0 +1,42 @@ +require 'abstract_unit' + +class ActiveModelHelperI18nTest < Test::Unit::TestCase + include ActionView::Context + include ActionView::Helpers::ActiveModelHelper + + attr_reader :request + + def setup + @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages']) + @object.stubs :to_model => @object + @object.stubs :class => stub(:model_name => stub(:human => "")) + + @object_name = 'book_seller' + @object_name_without_underscore = 'book seller' + + stubs(:content_tag).returns 'content_tag' + + I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" + I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + end + + def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never + error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') + end + + def test_error_messages_for_given_no_header_option_it_translates_header_message + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message' + error_messages_for(:object => @object, :locale => 'en') + end + + def test_error_messages_for_given_a_message_option_it_does_not_translate_message + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never + error_messages_for(:object => @object, :message => 'message', :locale => 'en') + end + + def test_error_messages_for_given_no_message_option_it_translates_message + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + error_messages_for(:object => @object, :locale => 'en') + end +end diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index c149070f2a..3e01ae78c3 100644 --- a/actionpack/test/template/active_record_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class ActiveRecordHelperTest < ActionView::TestCase +class ActiveModelHelperTest < ActionView::TestCase tests ActionView::Helpers::ActiveModelHelper silence_warnings do diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb deleted file mode 100644 index 047f81be29..0000000000 --- a/actionpack/test/template/active_record_helper_i18n_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'abstract_unit' - -class ActiveRecordHelperI18nTest < Test::Unit::TestCase - include ActionView::Context - include ActionView::Helpers::ActiveModelHelper - - attr_reader :request - - def setup - @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages']) - @object.stubs :to_model => @object - @object.stubs :class => stub(:model_name => stub(:human => "")) - - @object_name = 'book_seller' - @object_name_without_underscore = 'book seller' - - stubs(:content_tag).returns 'content_tag' - - I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" - I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' - end - - def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message - I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never - error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') - end - - def test_error_messages_for_given_no_header_option_it_translates_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message' - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en') - end - - def test_error_messages_for_given_a_message_option_it_does_not_translate_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :message => 'message', :locale => 'en') - end - - def test_error_messages_for_given_no_message_option_it_translates_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en') - end - - def test_error_messages_for_given_object_name_it_translates_object_name - I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name_without_underscore).returns "1 error prohibited this #{@object_name_without_underscore} from being saved" - I18n.expects(:t).with(@object_name, :default => @object_name_without_underscore, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name_without_underscore - error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name) - end -end diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb index 05a14f3554..68e790cf46 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -48,8 +48,7 @@ class PeopleHelperTest < ActionView::TestCase def with_test_route_set with_routing do |set| set.draw do |map| - map.people 'people', :controller => 'people', :action => 'index' - map.connect ':controller/:action/:id' + match 'people', :to => 'people#index', :as => :people end yield end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index cec53e479c..bf0b4ad3a7 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -26,7 +26,7 @@ class UrlHelperTest < ActionView::TestCase assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => true) assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false) end - + def test_url_for_escaping_is_safety_aware assert url_for(:a => 'b', :c => 'd', :escape => true).html_safe?, "escaped urls should be html_safe?" assert !url_for(:a => 'b', :c => 'd', :escape => false).html_safe?, "non-escaped urls shouldn't be safe" @@ -54,7 +54,7 @@ class UrlHelperTest < ActionView::TestCase path = @view.url_for(:controller => :cheeses, :foo => :bar, :baz => :quux) - assert_equal '/cheeses?baz=quux&foo=bar', path + assert_equal '/cheeses?baz=quux&foo=bar', sort_query_string_params(path) end # todo: missing test cases @@ -284,21 +284,21 @@ class UrlHelperTest < ActionView::TestCase assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end - + def test_current_page_ignoring_params @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end - + def test_current_page_with_params_that_match @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" }) assert current_page?("http://www.example.com/weblog/show?order=desc&page=1") end - + def test_link_unless_current @controller.request = RequestMock.new("http://www.example.com/weblog/show") @controller.url = "http://www.example.com/weblog/show" @@ -378,10 +378,17 @@ class UrlHelperTest < ActionView::TestCase assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end - + def protect_against_forgery? false end + + private + def sort_query_string_params(uri) + path, qs = uri.split('?') + qs = qs.split('&').sort.join('&') if qs + qs ? "#{path}?#{qs}" : path + end end class UrlHelperController < ActionController::Base diff --git a/activemodel/Gemfile b/activemodel/Gemfile deleted file mode 100644 index 289d696792..0000000000 --- a/activemodel/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -rails_root = Pathname.new(File.dirname(__FILE__)).join("..") - -Gem.sources.each { |uri| source uri } - -gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport") - -only :test do - gem "sqlite3-ruby" -end - -disable_system_gems diff --git a/activemodel/Rakefile b/activemodel/Rakefile index fd69a557aa..8897987518 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -11,7 +11,7 @@ require 'rake/testtask' task :default => :test -Rake::TestTask.new do |t| +Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("test/cases/**/*_test.rb").sort t.verbose = true @@ -47,6 +47,4 @@ spec = eval(File.read('activemodel.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 67f529262d..505e16c195 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -39,8 +39,10 @@ module ActiveModel 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' module Serializers diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 7a48960f89..e8bb62953d 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -93,7 +93,7 @@ module ActiveModel # company = Company.create(:address => '123 First St.') # company.errors.full_messages # => # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] - def full_messages(options = {}) + def full_messages full_messages = [] each do |attribute, messages| @@ -103,8 +103,10 @@ module ActiveModel if attribute == :base messages.each {|m| full_messages << m } else - attr_name = attribute.to_s.humanize - prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ') + attr_name = @base.class.human_attribute_name(attribute) + options = { :default => ' ', :scope => @base.class.i18n_scope } + prefix = attr_name + I18n.t(:"errors.format.separator", options) + messages.each do |m| full_messages << "#{prefix}#{m}" end @@ -135,10 +137,7 @@ module ActiveModel def generate_message(attribute, message = :invalid, options = {}) message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - klass_ancestors = [@base.class] - klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)} - - defaults = klass_ancestors.map do |klass| + defaults = @base.class.lookup_ancestors.map do |klass| [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", :"models.#{klass.name.underscore}.#{message}" ] end @@ -150,10 +149,10 @@ module ActiveModel value = @base.send(:read_attribute_for_validation, attribute) options = { :default => defaults, - :model => @base.class.name.humanize, - :attribute => attribute.to_s.humanize, + :model => @base.class.model_name.human, + :attribute => @base.class.human_attribute_name(attribute), :value => value, - :scope => [:activemodel, :errors] + :scope => [@base.class.i18n_scope, :errors] }.merge(options) I18n.translate(key, options) diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index b8c2a367b4..675d62b9a6 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -5,12 +5,13 @@ module ActiveModel attr_reader :singular, :plural, :element, :collection, :partial_path, :human alias_method :cache_key, :collection - def initialize(name) - super + def initialize(klass, name) + super(name) + @klass = klass @singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze @plural = ActiveSupport::Inflector.pluralize(@singular).freeze @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze - @human = @element.gsub(/_/, " ") + @human = ActiveSupport::Inflector.humanize(@element).freeze @collection = ActiveSupport::Inflector.tableize(self).freeze @partial_path = "#{@collection}/#{@element}".freeze end @@ -20,7 +21,7 @@ module ActiveModel # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. def model_name - @_model_name ||= ActiveModel::Name.new(name) + @_model_name ||= ActiveModel::Name.new(self, name) end end end diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb new file mode 100644 index 0000000000..2ad8ca9dea --- /dev/null +++ b/activemodel/lib/active_model/translation.rb @@ -0,0 +1,62 @@ +module ActiveModel + module Translation + include ActiveModel::Naming + + # Returns the i18n_scope for the class. Overwrite if you want custom lookup. + def i18n_scope + :activemodel + end + + # When localizing a string, goes through the lookup returned by this method. + # Used in ActiveModel::Name#human, ActiveModel::Errors#full_messages and + # ActiveModel::Translation#human_attribute_name. + def lookup_ancestors + self.ancestors.select { |x| x.respond_to?(:model_name) } + end + + # Transforms attributes names into a more human format, such as "First name" instead of "first_name". + # + # Example: + # + # Person.human_attribute_name("first_name") # => "First name" + # + # Specify +options+ with additional translating options. + def human_attribute_name(attribute, options = {}) + defaults = lookup_ancestors.map do |klass| + :"#{klass.model_name.underscore}.#{attribute}" + end + + defaults << options.delete(:default) if options[:default] + defaults << attribute.to_s.humanize + + options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults + I18n.translate(defaults.shift, options) + end + + # Model.human_name is deprecated. Use Model.model_name.human instead. + def human_name(*args) + ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,1]) + model_name.human(*args) + end + end + + class Name < String + # Transform the model name into a more humane format, using I18n. By default, + # it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post"). + # Specify +options+ with additional translating options. + def human(options={}) + return @human unless @klass.respond_to?(:lookup_ancestors) && + @klass.respond_to?(:i18n_scope) + + defaults = @klass.lookup_ancestors.map do |klass| + klass.model_name.underscore.to_sym + end + + defaults << options.delete(:default) if options[:default] + defaults << @human + + options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults + I18n.translate(defaults.shift, options) + end + end +end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 0f178a07c8..064ec98f3a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -7,6 +7,7 @@ module ActiveModel include ActiveSupport::Callbacks included do + extend ActiveModel::Translation define_callbacks :validate, :scope => :name end diff --git a/activerecord/lib/active_record/validator.rb b/activemodel/lib/active_model/validator.rb index 83a33f4dcd..09de72b757 100644 --- a/activerecord/lib/active_record/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,12 +1,12 @@ -module ActiveRecord #:nodoc: +module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveRecord::Base.validates_with + # A simple base class that can be used along with ActiveModel::Base.validates_with # - # class Person < ActiveRecord::Base + # class Person < ActiveModel::Base # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # if some_complex_logic # record.errors[:base] = "This record is invalid" @@ -19,14 +19,14 @@ module ActiveRecord #:nodoc: # end # end # - # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>, + # Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>, # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>. # - # class Person < ActiveRecord::Base + # class Person < ActiveModel::Base # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # record # => The person instance being validated # options # => Any non-standard options passed to validates_with @@ -36,7 +36,7 @@ module ActiveRecord #:nodoc: # To cause a validation error, you must add to the <tt>record<tt>'s errors directly # from within the validators message # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # record.errors[:base] << "This is some custom error message" # record.errors[:first_name] << "This is some complex validation" @@ -46,7 +46,7 @@ module ActiveRecord #:nodoc: # # To add behavior to the initialize method, use the following signature: # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def initialize(record, options) # super # @my_custom_field = options[:field_name] || :first_name diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index e54c85938b..49783c2735 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,13 +1,12 @@ -bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment" -if File.exist?("#{bundled}.rb") - require bundled -else - $:.unshift(File.dirname(__FILE__) + '/../../lib') - $:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib') +root = File.expand_path('../../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/activemodel/lib") end require 'config' - require 'active_model' # Show backtraces for deprecated behavior for quicker cleanup. diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 4d97af3d13..fe1ea36450 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' class NamingTest < ActiveModel::TestCase def setup - @model_name = ActiveModel::Name.new('Post::TrackBack') + @model_name = ActiveModel::Name.new(self, 'Post::TrackBack') end def test_singular diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb new file mode 100644 index 0000000000..d171784963 --- /dev/null +++ b/activemodel/test/cases/translation_test.rb @@ -0,0 +1,51 @@ +require 'cases/helper' + +class SuperUser + extend ActiveModel::Translation +end + +class User < SuperUser +end + +class ActiveModelI18nTests < ActiveModel::TestCase + + def setup + I18n.backend = I18n::Backend::Simple.new + end + + def test_translated_model_attributes + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', SuperUser.human_attribute_name('name') + end + + def test_translated_model_attributes_with_symbols + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', SuperUser.human_attribute_name(:name) + end + + def test_translated_model_attributes_with_ancestor + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:user => {:name => 'user name attribute'} } } + assert_equal 'user name attribute', User.human_attribute_name('name') + end + + def test_translated_model_attributes_with_ancestors_fallback + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', User.human_attribute_name('name') + end + + def test_translated_model_names + I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} } + assert_equal 'super_user model', SuperUser.model_name.human + end + + def test_translated_model_names_with_sti + I18n.backend.store_translations 'en', :activemodel => {:models => {:user => 'user model'} } + assert_equal 'user model', User.model_name.human + end + + def test_translated_model_names_with_ancestors_fallback + I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} } + assert_equal 'super_user model', User.model_name.human + end +end + diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 443a81c6ac..54b2405c92 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -63,7 +63,6 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index fc4f1926b0..68b1b27f75 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -56,6 +56,12 @@ class I18nValidationTest < ActiveModel::TestCase @person.errors.add_on_blank :title, 'custom' end + def test_errors_full_messages_translates_human_attribute_name_for_model_attributes + @person.errors.add('name', 'empty') + I18n.expects(:translate).with(:"person.name", :default => ['Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name') + @person.errors.full_messages + end + # ActiveRecord::Validations # validates_confirmation_of w/ mocha def test_validates_confirmation_of_generates_message @@ -494,6 +500,8 @@ class I18nValidationTest < ActiveModel::TestCase assert_equal ['global message'], @person.errors[:title] end + # test with validates_with + def test_validations_with_message_symbol_must_translate I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}} Person.validates_presence_of :title, :message => :custom_error diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index c290b49a28..fae87a6188 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -13,24 +13,24 @@ class ValidatesWithTest < ActiveRecord::TestCase ERROR_MESSAGE = "Validation error from validator" OTHER_ERROR_MESSAGE = "Validation error from other validator" - class ValidatorThatAddsErrors < ActiveRecord::Validator + class ValidatorThatAddsErrors < ActiveModel::Validator def validate() record.errors[:base] << ERROR_MESSAGE end end - class OtherValidatorThatAddsErrors < ActiveRecord::Validator + class OtherValidatorThatAddsErrors < ActiveModel::Validator def validate() record.errors[:base] << OTHER_ERROR_MESSAGE end end - class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator + class ValidatorThatDoesNotAddErrors < ActiveModel::Validator def validate() end end - class ValidatorThatValidatesOptions < ActiveRecord::Validator + class ValidatorThatValidatesOptions < ActiveModel::Validator def validate() if options[:field] == :first_name record.errors[:base] << ERROR_MESSAGE diff --git a/activerecord/Gemfile b/activerecord/Gemfile deleted file mode 100644 index 9cf37b0af0..0000000000 --- a/activerecord/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -Gem.sources.each { |uri| source uri } -sibling = "#{File.dirname(__FILE__)}/.." - -gem "activesupport", "3.0.pre", :vendored_at => "#{sibling}/activesupport" -gem "activemodel", "3.0.pre", :vendored_at => "#{sibling}/activemodel" -gem "arel", :git => "git://github.com/rails/arel.git", :branch => 'master' - -only :test do - gem "mocha" -end diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 1d8062e042..f7585f789b 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -195,8 +195,6 @@ spec = eval(File.read('activerecord.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index bae027bd2a..204cddde47 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '= 3.0.pre') s.add_dependency('activemodel', '= 3.0.pre') - s.add_dependency('arel', '~> 0.1.0') + s.add_dependency('arel', '~> 0.1.1') s.require_path = 'lib' s.autorequire = 'active_record' diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2d66fa9fcb..8195e78826 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -21,16 +21,11 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -bundled = "#{File.dirname(__FILE__)}/../vendor/gems/environment" -if File.exist?("#{bundled}.rb") - require bundled -else - activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" - $:.unshift(activesupport_path) if File.directory?(activesupport_path) +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) -end +activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib" +$:.unshift(activemodel_path) if File.directory?(activemodel_path) require 'active_support' require 'active_model' @@ -51,6 +46,7 @@ module ActiveRecord 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' @@ -74,7 +70,7 @@ module ActiveRecord autoload :TestCase, 'active_record/test_case' autoload :Timestamp, 'active_record/timestamp' autoload :Transactions, 'active_record/transactions' - autoload :Validator, 'active_record/validator' + autoload :Types, 'active_record/types' autoload :Validations, 'active_record/validations' module AttributeMethods @@ -87,6 +83,20 @@ module ActiveRecord autoload :Write, 'active_record/attribute_methods/write' end + module Attributes + autoload :Aliasing, 'active_record/attributes/aliasing' + autoload :Store, 'active_record/attributes/store' + autoload :Typecasting, 'active_record/attributes/typecasting' + 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' + end + module Locking autoload :Optimistic, 'active_record/locking/optimistic' autoload :Pessimistic, 'active_record/locking/pessimistic' diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index a4e144f233..74921241f7 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -8,25 +8,18 @@ module ActiveRecord end def read_attribute_before_type_cast(attr_name) - @attributes[attr_name] + _attributes.without_typecast[attr_name] end # Returns a hash of attributes before typecasting and deserialization. def attributes_before_type_cast - self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute_before_type_cast(name) - attrs - end + _attributes.without_typecast end private # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) - if attribute_name == 'id' - read_attribute_before_type_cast(self.class.primary_key) - else - read_attribute_before_type_cast(attribute_name) - end + read_attribute_before_type_cast(attribute_name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index a949d80120..0154ee35f8 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -8,23 +8,7 @@ module ActiveRecord end def query_attribute(attr_name) - unless value = read_attribute(attr_name) - false - else - column = self.class.columns_hash[attr_name] - if column.nil? - if Numeric === value || value !~ /[^0-9]/ - !value.to_i.zero? - else - return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) - !value.blank? - end - elsif column.number? - !value.zero? - else - !value.blank? - end - end + _attributes.has?(attr_name) end private @@ -35,3 +19,5 @@ module ActiveRecord end end end + + diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 3da3d9d8cc..97caec7744 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -37,11 +37,7 @@ module ActiveRecord protected def define_method_attribute(attr_name) - if self.serialized_attributes[attr_name] - define_read_method_for_serialized_attribute(attr_name) - else - define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) - end + define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) if attr_name == primary_key && attr_name != "id" define_read_method(:id, attr_name, columns_hash[attr_name]) @@ -49,18 +45,12 @@ module ActiveRecord end private - # Define read method for serialized attribute. - def define_read_method_for_serialized_attribute(attr_name) - generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) - end # Define an attribute reader method. Cope with nil column. def define_read_method(symbol, attr_name, column) - cast_code = column.type_cast_code('v') if column - access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" - + access_code = "_attributes['#{attr_name}']" unless attr_name.to_s == self.primary_key.to_s - access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") + access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless _attributes.key?('#{attr_name}'); ") end if cache_attribute?(attr_name) @@ -73,38 +63,7 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' - if !(value = @attributes[attr_name]).nil? - if column = column_for_attribute(attr_name) - if unserializable_attribute?(attr_name, column) - unserialize_attribute(attr_name) - else - column.type_cast(value) - end - else - value - end - else - nil - end - end - - # Returns true if the attribute is of a text column and marked for serialization. - def unserializable_attribute?(attr_name, column) - column.text? && self.class.serialized_attributes[attr_name] - end - - # Returns the unserialized object of the attribute. - def unserialize_attribute(attr_name) - unserialized_object = object_from_yaml(@attributes[attr_name]) - - if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil? - @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object - else - raise SerializationTypeMismatch, - "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}" - end + _attributes[attr_name] end private diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index a8e3e28a7a..4ac0c7f608 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -12,48 +12,20 @@ module ActiveRecord end module ClassMethods + + def cache_attribute?(attr_name) + time_zone_aware?(attr_name) || super + end + protected - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. - def define_method_attribute(attr_name) - if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) - method_body = <<-EOV - def #{attr_name}(reload = false) - cached = @attributes_cache['#{attr_name}'] - return cached if cached && !reload - time = read_attribute('#{attr_name}') - @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time - end - EOV - generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) - else - super - end - end - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. - def define_method_attribute=(attr_name) - if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) - method_body = <<-EOV - def #{attr_name}=(time) - unless time.acts_like?(:time) - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time - end - time = time.in_time_zone rescue nil if time - write_attribute(:#{attr_name}, time) - end - EOV - generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) - else - super - end + def time_zone_aware?(attr_name) + column = columns_hash[attr_name] + time_zone_aware_attributes && + !skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym) && + [:datetime, :timestamp].include?(column.type) end - private - def create_time_zone_conversion_attribute?(name, column) - time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e31acac050..37eadbe0a9 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,14 +17,9 @@ module ActiveRecord # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float # columns are turned into +nil+. def write_attribute(attr_name, value) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' + attr_name = _attributes.unalias(attr_name) @attributes_cache.delete(attr_name) - if (column = column_for_attribute(attr_name)) && column.number? - @attributes[attr_name] = convert_number_column_value(value) - else - @attributes[attr_name] = value - end + _attributes[attr_name] = value end private diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb new file mode 100644 index 0000000000..e4d9e89821 --- /dev/null +++ b/activerecord/lib/active_record/attributes.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Attributes + + # Returns true if the given attribute is in the attributes hash + def has_attribute?(attr_name) + _attributes.key?(attr_name) + end + + # Returns an array of names for the attributes available on this object sorted alphabetically. + def attribute_names + _attributes.keys.sort! + end + + # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. + def attributes + attributes = _attributes.dup + attributes.typecast! unless _attributes.frozen? + attributes.to_h + end + + protected + + # Not to be confused with the public #attributes method, which returns a typecasted Hash. + def _attributes + @attributes + end + + def initialize_attribute_store(merge_attributes = nil) + @attributes = ActiveRecord::Attributes::Store.new + @attributes.merge!(merge_attributes) if merge_attributes + @attributes.types.merge!(self.class.attribute_types) + @attributes.aliases.merge!('id' => self.class.primary_key) unless 'id' == self.class.primary_key + @attributes + end + + end +end diff --git a/activerecord/lib/active_record/attributes/aliasing.rb b/activerecord/lib/active_record/attributes/aliasing.rb new file mode 100644 index 0000000000..db77739d1f --- /dev/null +++ b/activerecord/lib/active_record/attributes/aliasing.rb @@ -0,0 +1,42 @@ +module ActiveRecord + module Attributes + module Aliasing + # Allows access to keys using aliased names. + # + # Example: + # class Attributes < Hash + # include Aliasing + # end + # + # attributes = Attributes.new + # attributes.aliases['id'] = 'fancy_primary_key' + # attributes['fancy_primary_key'] = 2020 + # + # attributes['id'] + # => 2020 + # + # Additionally, symbols are always aliases of strings: + # attributes[:fancy_primary_key] + # => 2020 + # + def [](key) + super(unalias(key)) + end + + def []=(key, value) + super(unalias(key), value) + end + + def aliases + @aliases ||= {} + end + + def unalias(key) + key = key.to_s + aliases[key] || key + end + + end + end +end + diff --git a/activerecord/lib/active_record/attributes/store.rb b/activerecord/lib/active_record/attributes/store.rb new file mode 100644 index 0000000000..61109f4acc --- /dev/null +++ b/activerecord/lib/active_record/attributes/store.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module Attributes + class Store < Hash + include ActiveRecord::Attributes::Typecasting + include ActiveRecord::Attributes::Aliasing + + # Attributes not mapped to a column are handled using Type::Unknown, + # which enables boolean typecasting for unmapped keys. + def types + @types ||= Hash.new(Type::Unknown.new) + end + + end + end +end diff --git a/activerecord/lib/active_record/attributes/typecasting.rb b/activerecord/lib/active_record/attributes/typecasting.rb new file mode 100644 index 0000000000..56c32f9895 --- /dev/null +++ b/activerecord/lib/active_record/attributes/typecasting.rb @@ -0,0 +1,117 @@ +module ActiveRecord + module Attributes + module Typecasting + # Typecasts values during access based on their key mapping to a Type. + # + # Example: + # class Attributes < Hash + # include Typecasting + # end + # + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # attributes['comments_count'] = '5' + # + # attributes['comments_count'] + # => 5 + # + # To support keys not mapped to a typecaster, add a default to types. + # attributes.types.default = Type::Unknown + # attributes['age'] = '25' + # attributes['age'] + # => '25' + # + # A valid type supports #cast, #precast, #boolean, and #appendable? methods. + # + def [](key) + value = super(key) + typecast_read(key, value) + end + + def []=(key, value) + super(key, typecast_write(key, value)) + end + + def to_h + hash = {} + hash.merge!(self) + hash + end + + def dup # :nodoc: + copy = super + copy.types = types.dup + copy + end + + # Provides a duplicate with typecasting disabled. + # + # Example: + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # attributes['comments_count'] = '5' + # + # attributes.without_typecast['comments_count'] + # => '5' + # + def without_typecast + dup.without_typecast! + end + + def without_typecast! + types.clear + self + end + + def typecast! + keys.each { |key| self[key] = self[key] } + self + end + + # Check if key has a value that typecasts to true. + # + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # + # attributes['comments_count'] = 0 + # attributes.has?('comments_count') + # => false + # + # attributes['comments_count'] = 1 + # attributes.has?('comments_count') + # => true + # + def has?(key) + value = self[key] + boolean_typecast(key, value) + end + + def types + @types ||= {} + end + + protected + + def types=(other_types) + @types = other_types + end + + def boolean_typecast(key, value) + value ? types[key].boolean(value) : false + end + + def typecast_read(key, value) + type = types[key] + value = type.cast(value) + self[key] = value if type.appendable? && !frozen? + + value + end + + def typecast_write(key, value) + types[key].precast(value) + end + + end + end +end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 283aa7ddfc..4e6090458a 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,5 +1,7 @@ +require 'benchmark' require 'yaml' require 'set' +require 'active_support/benchmarkable' require 'active_support/dependencies' require 'active_support/time' require 'active_support/core_ext/class/attribute_accessors' @@ -1384,7 +1386,8 @@ module ActiveRecord #:nodoc: subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information } end - def self_and_descendants_from_active_record#nodoc: + # Set the lookup ancestors for ActiveModel. + def lookup_ancestors #:nodoc: klass = self classes = [klass] while klass != klass.base_class @@ -1398,32 +1401,9 @@ module ActiveRecord #:nodoc: [self] end - # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example: - # Person.human_attribute_name("first_name") # => "First name" - # This used to be deprecated in favor of humanize, but is now preferred, because it automatically uses the I18n - # module now. - # Specify +options+ with additional translating options. - def human_attribute_name(attribute_key_name, options = {}) - defaults = self_and_descendants_from_active_record.map do |klass| - :"#{klass.name.underscore}.#{attribute_key_name}" - end - defaults << options[:default] if options[:default] - defaults.flatten! - defaults << attribute_key_name.to_s.humanize - options[:count] ||= 1 - I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes])) - end - - # Transform the modelname into a more humane format, using I18n. - # By default, it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post"). - # Default scope of the translation is activerecord.models - # Specify +options+ with additional translating options. - def human_name(options = {}) - defaults = self_and_descendants_from_active_record.map do |klass| - :"#{klass.name.underscore}" - end - defaults << self.name.underscore.humanize - I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options)) + # Set the i18n scope to overwrite ActiveModel. + def i18n_scope #:nodoc: + :activerecord end # True if this isn't a concrete subclass needing a STI type condition. @@ -1463,38 +1443,6 @@ module ActiveRecord #:nodoc: connection.quote(object) end - # Log and benchmark multiple statements in a single block. Example: - # - # Project.benchmark("Creating project") do - # project = Project.create("name" => "stuff") - # project.create_manager("name" => "David") - # project.milestones << Milestone.find(:all) - # end - # - # The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>, - # which makes it easy to include benchmarking statements in production software that will remain inexpensive because - # the benchmark will only be conducted if the log level is low enough. - # - # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level <= log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, '%s (%.1fms)' % [title, ms]) - result - else - yield - end - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, Logger::ERROR if logger - yield - ensure - logger.level = old_logger_level if logger - end - # Overwrite the default class equality method to provide support for association proxies. def ===(object) object.is_a?(self) @@ -1674,7 +1622,7 @@ module ActiveRecord #:nodoc: def instantiate(record) object = find_sti_class(record[inheritance_column]).allocate - object.instance_variable_set(:'@attributes', record) + object.send(:initialize_attribute_store, record) object.instance_variable_set(:'@attributes_cache', {}) object.send(:_run_find_callbacks) @@ -2445,7 +2393,7 @@ module ActiveRecord #:nodoc: # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. def initialize(attributes = nil) - @attributes = attributes_from_column_definition + initialize_attribute_store(attributes_from_column_definition) @attributes_cache = {} @new_record = true ensure_proper_type @@ -2471,7 +2419,7 @@ module ActiveRecord #:nodoc: callback(:after_initialize) if respond_to_without_attributes?(:after_initialize) cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) cloned_attributes.delete(self.class.primary_key) - @attributes = cloned_attributes + initialize_attribute_store(cloned_attributes) clear_aggregation_cache @attributes_cache = {} @new_record = true @@ -2697,7 +2645,7 @@ module ActiveRecord #:nodoc: def reload(options = nil) clear_aggregation_cache clear_association_cache - @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) + _attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) @attributes_cache = {} self end @@ -2794,16 +2742,6 @@ module ActiveRecord #:nodoc: !value.blank? end - # Returns true if the given attribute is in the attributes hash - def has_attribute?(attr_name) - @attributes.has_key?(attr_name.to_s) - end - - # Returns an array of names for the attributes available on this object sorted alphabetically. - def attribute_names - @attributes.keys.sort - end - # Returns the column object for the named attribute. def column_for_attribute(name) self.class.columns_hash[name.to_s] @@ -2927,18 +2865,6 @@ module ActiveRecord #:nodoc: end end - def convert_number_column_value(value) - if value == false - 0 - elsif value == true - 1 - elsif value.is_a?(String) && value.blank? - nil - else - value - end - end - def remove_attributes_protected_from_mass_assignment(attributes) safe_attributes = if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil? @@ -3057,7 +2983,7 @@ module ActiveRecord #:nodoc: end def instantiate_time_object(name, values) - if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name)) + if self.class.send(:time_zone_aware?, name) Time.zone.local(*values) else Time.time_with_datetime_fallback(@@default_timezone, *values) @@ -3144,15 +3070,13 @@ module ActiveRecord #:nodoc: comma_pair_list(quote_columns(quoter, hash)) end - def object_from_yaml(string) - return string unless string.is_a?(String) && string =~ /^---/ - YAML::load(string) rescue string - end end Base.class_eval do extend ActiveModel::Naming extend QueryCache::ClassMethods + extend ActiveSupport::Benchmarkable + include Validations include Locking::Optimistic, Locking::Pessimistic include AttributeMethods @@ -3160,6 +3084,7 @@ module ActiveRecord #:nodoc: include AttributeMethods::PrimaryKey include AttributeMethods::TimeZoneConversion include AttributeMethods::Dirty + include Attributes, Types include Callbacks, ActiveModel::Observing, Timestamp include Associations, AssociationPreload, NamedScope include ActiveModel::Conversion @@ -3169,6 +3094,7 @@ module ActiveRecord #:nodoc: include AutosaveAssociation, NestedAttributes include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization + end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 12253eac3f..377f2a44c5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -211,9 +211,10 @@ module ActiveRecord # calling +checkout+ on this pool. def checkin(conn) @connection_mutex.synchronize do - conn.run_callbacks :checkin - @checked_out.delete conn - @queue.signal + conn.run_callbacks :checkin do + @checked_out.delete conn + @queue.signal + end end end @@ -255,9 +256,10 @@ module ActiveRecord end def checkout_and_verify(c) - c.verify! - c.run_callbacks :checkout - @checked_out << c + c.run_callbacks :checkout do + c.verify! + @checked_out << c + end c end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 694e1e561c..8fae26b790 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,4 +1,3 @@ -require 'benchmark' require 'date' require 'bigdecimal' require 'bigdecimal/util' @@ -12,8 +11,6 @@ require 'active_record/connection_adapters/abstract/connection_pool' require 'active_record/connection_adapters/abstract/connection_specification' require 'active_record/connection_adapters/abstract/query_cache' -require 'active_support/core_ext/benchmark' - module ActiveRecord module ConnectionAdapters # :nodoc: # ActiveRecord supports multiple database systems. AbstractAdapter and @@ -33,6 +30,7 @@ module ActiveRecord include Quoting, DatabaseStatements, SchemaStatements include QueryCache include ActiveSupport::Callbacks + define_callbacks :checkout, :checkin @@row_even = true @@ -193,6 +191,7 @@ module ActiveRecord end def log_info(sql, name, ms) + @runtime += ms if @logger && @logger.debug? name = '%s (%.1fms)' % [name || 'SQL', ms] @logger.debug(format_log_entry(name, sql.squeeze(' '))) @@ -200,13 +199,8 @@ module ActiveRecord end protected - def log(sql, name) - event = ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name) do - yield if block_given? - end - @runtime += event.duration - log_info(sql, name, event.duration) - event.result + def log(sql, name, &block) + ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name, &block) rescue Exception => e # Log message and raise exception. # Set last_verification to 0, so that connection gets verified diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5a49fc2d2f..c9c2892ba4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -11,11 +11,11 @@ module ActiveRecord raise ArgumentError, "No database file specified. Missing argument: database" end - # Allow database path relative to RAILS_ROOT, but only if + # Allow database path relative to Rails.root, but only if # the database path is not the special path that tells # Sqlite to build a database only in memory. - if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database] - config[:database] = File.expand_path(config[:database], RAILS_ROOT) + if Object.const_defined?(:Rails) && ':memory:' != config[:database] + config[:database] = File.expand_path(config[:database], Rails.root) end end end diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb new file mode 100644 index 0000000000..a5ce7ac524 --- /dev/null +++ b/activerecord/lib/active_record/notifications.rb @@ -0,0 +1,5 @@ +require 'active_support/notifications' + +ActiveSupport::Notifications.subscribe("sql") do |event| + ActiveRecord::Base.connection.log_info(event.payload[:sql], event.payload[:name], event.duration) +end diff --git a/activerecord/lib/active_record/types.rb b/activerecord/lib/active_record/types.rb new file mode 100644 index 0000000000..74f569352b --- /dev/null +++ b/activerecord/lib/active_record/types.rb @@ -0,0 +1,38 @@ +module ActiveRecord + module Types + extend ActiveSupport::Concern + + module ClassMethods + + def attribute_types + attribute_types = {} + columns.each do |column| + options = {} + options[:time_zone_aware] = time_zone_aware?(column.name) + options[:serialize] = serialized_attributes[column.name] + + attribute_types[column.name] = to_type(column, options) + end + attribute_types + end + + private + + def to_type(column, options = {}) + type_class = if options[:time_zone_aware] + Type::TimeWithZone + elsif options[:serialize] + Type::Serialize + elsif [ :integer, :float, :decimal ].include?(column.type) + Type::Number + else + Type::Object + end + + type_class.new(column, options) + end + + end + + end +end diff --git a/activerecord/lib/active_record/types/number.rb b/activerecord/lib/active_record/types/number.rb new file mode 100644 index 0000000000..cfbe877575 --- /dev/null +++ b/activerecord/lib/active_record/types/number.rb @@ -0,0 +1,30 @@ +module ActiveRecord + module Type + class Number < Object + + def boolean(value) + value = cast(value) + !(value.nil? || value.zero?) + end + + def precast(value) + convert_number_column_value(value) + end + + private + + def convert_number_column_value(value) + if value == false + 0 + elsif value == true + 1 + elsif value.is_a?(String) && value.blank? + nil + else + value + end + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/object.rb b/activerecord/lib/active_record/types/object.rb new file mode 100644 index 0000000000..ec3f861abd --- /dev/null +++ b/activerecord/lib/active_record/types/object.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Type + module Casting + + def cast(value) + typecaster.type_cast(value) + end + + def precast(value) + value + end + + def boolean(value) + cast(value).present? + end + + # Attributes::Typecasting stores appendable? types (e.g. serialized Arrays) when typecasting reads. + def appendable? + false + end + + end + + class Object + include Casting + + attr_reader :name, :options + attr_reader :typecaster + + def initialize(typecaster = nil, options = {}) + @typecaster, @options = typecaster, options + end + + end + + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/serialize.rb b/activerecord/lib/active_record/types/serialize.rb new file mode 100644 index 0000000000..7b6af1981f --- /dev/null +++ b/activerecord/lib/active_record/types/serialize.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Type + class Serialize < Object + + def cast(value) + unserialize(value) + end + + def appendable? + true + end + + protected + + def unserialize(value) + unserialized_object = object_from_yaml(value) + + if unserialized_object.is_a?(@options[:serialize]) || unserialized_object.nil? + unserialized_object + else + raise SerializationTypeMismatch, + "#{name} was supposed to be a #{@options[:serialize]}, but was a #{unserialized_object.class.to_s}" + end + end + + def object_from_yaml(string) + return string unless string.is_a?(String) && string =~ /^---/ + YAML::load(string) rescue string + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/time_with_zone.rb b/activerecord/lib/active_record/types/time_with_zone.rb new file mode 100644 index 0000000000..3a8b9292f9 --- /dev/null +++ b/activerecord/lib/active_record/types/time_with_zone.rb @@ -0,0 +1,20 @@ +module ActiveRecord + module Type + class TimeWithZone < Object + + def cast(time) + time = super(time) + time.acts_like?(:time) ? time.in_time_zone : time + end + + def precast(time) + unless time.acts_like?(:time) + time = time.is_a?(String) ? ::Time.zone.parse(time) : time.to_time rescue time + end + time = time.in_time_zone rescue nil if time + super(time) + end + + end + end +end diff --git a/activerecord/lib/active_record/types/unknown.rb b/activerecord/lib/active_record/types/unknown.rb new file mode 100644 index 0000000000..f832c7b304 --- /dev/null +++ b/activerecord/lib/active_record/types/unknown.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Type + # Useful for handling attributes not mapped to types. Performs some boolean typecasting, + # but otherwise leaves the value untouched. + class Unknown + + def cast(value) + value + end + + def precast(value) + value + end + + # Attempts typecasting to handle numeric, false and blank values. + def boolean(value) + empty = (numeric?(value) && value.to_i.zero?) || false?(value) || value.blank? + !empty + end + + def appendable? + false + end + + protected + + def false?(value) + ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) + end + + def numeric?(value) + Numeric === value || value !~ /[^0-9]/ + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index e61b253192..0365cb592f 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -17,90 +17,6 @@ module ActiveRecord end end - class Errors < ActiveModel::Errors - class << self - def default_error_messages - message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')." - ActiveSupport::Deprecation.warn(message) - - I18n.translate 'activerecord.errors.messages' - end - end - - # Returns all the full error messages in an array. - # - # class Company < ActiveRecord::Base - # validates_presence_of :name, :address, :email - # validates_length_of :name, :in => 5..30 - # end - # - # company = Company.create(:address => '123 First St.') - # company.errors.full_messages # => - # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] - def full_messages(options = {}) - full_messages = [] - - each do |attribute, messages| - messages = Array.wrap(messages) - next if messages.empty? - - if attribute == :base - messages.each {|m| full_messages << m } - else - attr_name = @base.class.human_attribute_name(attribute.to_s) - prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') - messages.each do |m| - full_messages << "#{prefix}#{m}" - end - end - end - - full_messages - end - - # Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>). - # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there, - # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the - # default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name, - # translated attribute name and the value are available for interpolation. - # - # When using inheritance in your models, it will check all the inherited models too, but only if the model itself - # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt> - # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations: - # - # <ol> - # <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li> - # <li><tt>activerecord.errors.models.admin.blank</tt></li> - # <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li> - # <li><tt>activerecord.errors.models.user.blank</tt></li> - # <li><tt>activerecord.errors.messages.blank</tt></li> - # <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li> - # </ol> - def generate_message(attribute, message = :invalid, options = {}) - message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - - defaults = @base.class.self_and_descendants_from_active_record.map do |klass| - [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", - :"models.#{klass.name.underscore}.#{message}" ] - end - - defaults << options.delete(:default) - defaults = defaults.compact.flatten << :"messages.#{message}" - - key = defaults.shift - value = @base.respond_to?(attribute) ? @base.send(attribute) : nil - - options = { :default => defaults, - :model => @base.class.human_name, - :attribute => @base.class.human_attribute_name(attribute.to_s), - :value => value, - :scope => [:activerecord, :errors] - }.merge(options) - - I18n.translate(key, options) - end - end - module Validations extend ActiveSupport::Concern @@ -165,11 +81,6 @@ module ActiveRecord errors.empty? end - - # Returns the Errors object that holds all information about attribute error messages. - def errors - @errors ||= Errors.new(self) - end end end end diff --git a/activerecord/test/cases/attributes/aliasing_test.rb b/activerecord/test/cases/attributes/aliasing_test.rb new file mode 100644 index 0000000000..7ee25779f1 --- /dev/null +++ b/activerecord/test/cases/attributes/aliasing_test.rb @@ -0,0 +1,20 @@ +require "cases/helper" + +class AliasingTest < ActiveRecord::TestCase + + class AliasingAttributes < Hash + include ActiveRecord::Attributes::Aliasing + end + + test "attribute access with aliasing" do + attributes = AliasingAttributes.new + attributes[:name] = 'Batman' + attributes.aliases['nickname'] = 'name' + + assert_equal 'Batman', attributes[:name], "Symbols should point to Strings" + assert_equal 'Batman', attributes['name'] + assert_equal 'Batman', attributes['nickname'] + assert_equal 'Batman', attributes[:nickname] + end + +end diff --git a/activerecord/test/cases/attributes/typecasting_test.rb b/activerecord/test/cases/attributes/typecasting_test.rb new file mode 100644 index 0000000000..8a3b551375 --- /dev/null +++ b/activerecord/test/cases/attributes/typecasting_test.rb @@ -0,0 +1,120 @@ +require "cases/helper" + +class TypecastingTest < ActiveRecord::TestCase + + class TypecastingAttributes < Hash + include ActiveRecord::Attributes::Typecasting + end + + module MockType + class Object + + def cast(value) + value + end + + def precast(value) + value + end + + def boolean(value) + !value.blank? + end + + def appendable? + false + end + + end + + class Integer < Object + + def cast(value) + value.to_i + end + + def precast(value) + value ? value : 0 + end + + def boolean(value) + !Float(value).zero? + end + + end + + class Serialize < Object + + def cast(value) + YAML::load(value) rescue value + end + + def precast(value) + value + end + + def appendable? + true + end + + end + end + + def setup + @attributes = TypecastingAttributes.new + @attributes.types.default = MockType::Object.new + @attributes.types['comments_count'] = MockType::Integer.new + end + + test "typecast on read" do + attributes = @attributes.merge('comments_count' => '5') + assert_equal 5, attributes['comments_count'] + end + + test "typecast on write" do + @attributes['comments_count'] = false + + assert_equal 0, @attributes.to_h['comments_count'] + end + + test "serialized objects" do + attributes = @attributes.merge('tags' => [ 'peanut butter' ].to_yaml) + attributes.types['tags'] = MockType::Serialize.new + attributes['tags'] << 'jelly' + + assert_equal [ 'peanut butter', 'jelly' ], attributes['tags'] + end + + test "without typecasting" do + @attributes.merge!('comments_count' => '5') + attributes = @attributes.without_typecast + + assert_equal '5', attributes['comments_count'] + assert_equal 5, @attributes['comments_count'], "Original attributes should typecast" + end + + + test "typecast all attributes" do + attributes = @attributes.merge('title' => 'I love sandwiches', 'comments_count' => '5') + attributes.typecast! + + assert_equal({ 'title' => 'I love sandwiches', 'comments_count' => 5 }, attributes) + end + + test "query for has? value" do + attributes = @attributes.merge('comments_count' => '1') + + assert_equal true, attributes.has?('comments_count') + attributes['comments_count'] = '0' + assert_equal false, attributes.has?('comments_count') + end + + test "attributes to Hash" do + attributes_hash = { 'title' => 'I love sandwiches', 'comments_count' => '5' } + attributes = @attributes.merge(attributes_hash) + + assert_equal Hash, attributes.to_h.class + assert_equal attributes_hash, attributes.to_h + end + +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index bfb2df313b..5c2911eca1 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -2195,9 +2195,9 @@ class BasicsTest < ActiveRecord::TestCase log = StringIO.new ActiveRecord::Base.logger = Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN - ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count } - ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count } - ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count } + ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } + ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } + ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count } assert_no_match /Debug Topic Count/, log.string assert_match /Warn Topic Count/, log.string assert_match /Error Topic Count/, log.string @@ -2209,8 +2209,8 @@ class BasicsTest < ActiveRecord::TestCase original_logger = ActiveRecord::Base.logger log = StringIO.new ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" } - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" } + ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" } + ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } assert_no_match /Loud/, log.string assert_match /Quiet/, log.string ensure diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index aa09c7061f..871cfa6468 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,5 +1,10 @@ -$:.unshift(File.dirname(__FILE__) + '/../../lib') -$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib') +root = File.expand_path('../../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/activerecord/lib") +end require 'config' diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index d59c53cec8..ae4dcfb81e 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -30,17 +30,17 @@ class ActiveRecordI18nTests < Test::Unit::TestCase def test_translated_model_names I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Topic.human_name + assert_equal 'topic model', Topic.model_name.human end def test_translated_model_names_with_sti I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} } - assert_equal 'reply model', Reply.human_name + assert_equal 'reply model', Reply.model_name.human end def test_translated_model_names_with_sti_fallback I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Reply.human_name + assert_equal 'topic model', Reply.model_name.human end end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bb9013c2a1..f9eea3d118 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -4,14 +4,14 @@ require "timeout" class PooledConnectionsTest < ActiveRecord::TestCase def setup - super + @per_test_teardown = [] @connection = ActiveRecord::Base.remove_connection end def teardown ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) - super + @per_test_teardown.each {|td| td.call } end def checkout_connections @@ -113,6 +113,23 @@ class PooledConnectionsTest < ActiveRecord::TestCase assert_equal 3, after_count - before_count end + def test_connection_pool_callbacks + checked_out, checked_in = false, false + ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do + set_callback(:checkout, :after) { checked_out = true } + set_callback(:checkin, :before) { checked_in = true } + end + @per_test_teardown << proc do + ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do + reset_callbacks :checkout + reset_callbacks :checkin + end + end + checkout_checkin_connections 1, 1 + assert checked_out + assert checked_in + end + private def add_record(name) diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index f3ed8ccd8d..99e248743a 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -15,8 +15,8 @@ class ReflectionTest < ActiveRecord::TestCase end def test_human_name - assert_equal "Price estimate", PriceEstimate.human_name - assert_equal "Subscriber", Subscriber.human_name + assert_equal "Price estimate", PriceEstimate.model_name.human + assert_equal "Subscriber", Subscriber.model_name.human end def test_column_null_not_null diff --git a/activerecord/test/cases/types/number_test.rb b/activerecord/test/cases/types/number_test.rb new file mode 100644 index 0000000000..ee7216a0f1 --- /dev/null +++ b/activerecord/test/cases/types/number_test.rb @@ -0,0 +1,30 @@ +require "cases/helper" + +class NumberTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('comments_count', 0, 'integer') + @number = ActiveRecord::Type::Number.new(@column) + end + + test "typecast" do + assert_equal 1, @number.cast(1) + assert_equal 1, @number.cast('1') + assert_equal 0, @number.cast('') + + assert_equal 0, @number.precast(false) + assert_equal 1, @number.precast(true) + assert_equal nil, @number.precast('') + assert_equal 0, @number.precast(0) + end + + test "cast as boolean" do + assert_equal true, @number.boolean('1') + assert_equal true, @number.boolean(1) + + assert_equal false, @number.boolean(0) + assert_equal false, @number.boolean('0') + assert_equal false, @number.boolean(nil) + end + +end diff --git a/activerecord/test/cases/types/object_test.rb b/activerecord/test/cases/types/object_test.rb new file mode 100644 index 0000000000..f2667a9b00 --- /dev/null +++ b/activerecord/test/cases/types/object_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" + +class ObjectTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('name', '', 'date') + @object = ActiveRecord::Type::Object.new(@column) + end + + test "typecast with column" do + date = Date.new(2009, 7, 10) + assert_equal date, @object.cast('10-07-2009') + assert_equal nil, @object.cast('') + + assert_equal date, @object.precast(date) + end + + test "cast as boolean" do + assert_equal false, @object.boolean(nil) + assert_equal false, @object.boolean('false') + assert_equal true, @object.boolean('10-07-2009') + end + +end diff --git a/activerecord/test/cases/types/serialize_test.rb b/activerecord/test/cases/types/serialize_test.rb new file mode 100644 index 0000000000..e9423a5b9d --- /dev/null +++ b/activerecord/test/cases/types/serialize_test.rb @@ -0,0 +1,20 @@ +require "cases/helper" + +class SerializeTest < ActiveRecord::TestCase + + test "typecast" do + serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array) + + assert_equal [], serializer.cast([].to_yaml) + assert_equal ['1'], serializer.cast(['1'].to_yaml) + assert_equal nil, serializer.cast(nil.to_yaml) + end + + test "cast as boolean" do + serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array) + + assert_equal true, serializer.boolean(['1'].to_yaml) + assert_equal false, serializer.boolean([].to_yaml) + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/types/time_with_zone_test.rb b/activerecord/test/cases/types/time_with_zone_test.rb new file mode 100644 index 0000000000..b3de79a6c8 --- /dev/null +++ b/activerecord/test/cases/types/time_with_zone_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" + +class TimeWithZoneTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('created_at', 0, 'datetime') + @time_with_zone = ActiveRecord::Type::TimeWithZone.new(@column) + end + + test "typecast" do + Time.use_zone("Pacific Time (US & Canada)") do + time_string = "2009-10-07 21:29:10" + time = Time.zone.parse(time_string) + + # assert_equal time, @time_with_zone.cast(time_string) + assert_equal nil, @time_with_zone.cast('') + assert_equal nil, @time_with_zone.cast(nil) + + assert_equal time, @time_with_zone.precast(time) + assert_equal time, @time_with_zone.precast(time_string) + assert_equal time, @time_with_zone.precast(time.to_time) + # assert_equal "#{time.to_date.to_s} 00:00:00 -0700", @time_with_zone.precast(time.to_date).to_s + end + end + + test "cast as boolean" do + Time.use_zone('Central Time (US & Canada)') do + time = Time.zone.now + + assert_equal true, @time_with_zone.boolean(time) + assert_equal true, @time_with_zone.boolean(time.to_date) + assert_equal true, @time_with_zone.boolean(time.to_time) + + assert_equal true, @time_with_zone.boolean(time.to_s) + assert_equal true, @time_with_zone.boolean(time.to_date.to_s) + assert_equal true, @time_with_zone.boolean(time.to_time.to_s) + + assert_equal false, @time_with_zone.boolean('') + end + end + +end diff --git a/activerecord/test/cases/types/unknown_test.rb b/activerecord/test/cases/types/unknown_test.rb new file mode 100644 index 0000000000..230d67b2fb --- /dev/null +++ b/activerecord/test/cases/types/unknown_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" + +class UnknownTest < ActiveRecord::TestCase + + test "typecast attributes does't modify values" do + unkown = ActiveRecord::Type::Unknown.new + person = { 'name' => '0' } + + assert_equal person['name'], unkown.cast(person['name']) + assert_equal person['name'], unkown.precast(person['name']) + end + + test "cast as boolean" do + person = { 'id' => 0, 'name' => ' ', 'admin' => 'false', 'votes' => '0' } + unkown = ActiveRecord::Type::Unknown.new + + assert_equal false, unkown.boolean(person['votes']) + assert_equal false, unkown.boolean(person['admin']) + assert_equal false, unkown.boolean(person['name']) + assert_equal false, unkown.boolean(person['id']) + + person = { 'id' => 5, 'name' => 'Eric', 'admin' => 'true', 'votes' => '25' } + assert_equal true, unkown.boolean(person['votes']) + assert_equal true, unkown.boolean(person['admin']) + assert_equal true, unkown.boolean(person['name']) + assert_equal true, unkown.boolean(person['id']) + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb new file mode 100644 index 0000000000..403a9a6e02 --- /dev/null +++ b/activerecord/test/cases/types_test.rb @@ -0,0 +1,32 @@ +require "cases/helper" +require 'models/topic' + +class TypesTest < ActiveRecord::TestCase + + test "attribute types from columns" do + begin + ActiveRecord::Base.time_zone_aware_attributes = true + attribute_type_classes = {} + Topic.attribute_types.each { |key, type| attribute_type_classes[key] = type.class } + + expected = { "id" => ActiveRecord::Type::Number, + "replies_count" => ActiveRecord::Type::Number, + "parent_id" => ActiveRecord::Type::Number, + "content" => ActiveRecord::Type::Serialize, + "written_on" => ActiveRecord::Type::TimeWithZone, + "title" => ActiveRecord::Type::Object, + "author_name" => ActiveRecord::Type::Object, + "approved" => ActiveRecord::Type::Object, + "parent_title" => ActiveRecord::Type::Object, + "bonus_time" => ActiveRecord::Type::Object, + "type" => ActiveRecord::Type::Object, + "last_read" => ActiveRecord::Type::Object, + "author_email_address" => ActiveRecord::Type::Object } + + assert_equal expected, attribute_type_classes + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + end + +end diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 3794a0ebb9..3f96d7973b 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -17,26 +17,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase } end - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') - end - - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') - end - - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') - end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') end @@ -45,107 +26,6 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') end - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) - end - - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') - end - - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) - end - - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') - end - - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) - end - - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') - end - - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) - end - - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') - end - - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) - end - - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end - - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) - end - - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) - end - - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') - end - - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) - end - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) def test_generate_message_taken_with_default_message assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') @@ -155,4 +35,13 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') end + # ActiveRecord#RecordInvalid exception + + test "RecordInvalid exception can be localized" do + topic = Topic.new + topic.errors.add(:title, :invalid) + topic.errors.add(:title, :blank) + assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message + end + end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 252138c0d6..532de67d99 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -30,20 +30,6 @@ class I18nValidationTest < ActiveRecord::TestCase end end - def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%s interpolation syntax was deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this') - end - end - - def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%d interpolation syntaxes are deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2) - end - end - # ActiveRecord::Errors def test_errors_generate_message_translates_custom_model_attribute_key I18n.expects(:translate).with( @@ -162,722 +148,4 @@ class I18nValidationTest < ActiveRecord::TestCase assert_equal ['global message'], replied_topic.errors[:replies] end - def test_errors_add_on_empty_generates_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) - @topic.errors.add_on_empty :title - end - - def test_errors_add_on_empty_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) - @topic.errors.add_on_empty :title, 'custom' - end - - def test_errors_add_on_blank_generates_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.errors.add_on_blank :title - end - - def test_errors_add_on_blank_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.errors.add_on_blank :title, 'custom' - end - - def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @topic.errors.add('title', 'empty') - I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title') - @topic.errors.full_messages :locale => 'en' - end - - # ActiveRecord::Validations - # validates_confirmation_of w/ mocha - def test_validates_confirmation_of_generates_message - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) - @topic.valid? - end - - def test_validates_confirmation_of_generates_message_with_custom_default_message - Topic.validates_confirmation_of :title, :message => 'custom' - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) - @topic.valid? - end - - # validates_acceptance_of w/ mocha - - def test_validates_acceptance_of_generates_message - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) - @topic.valid? - end - - def test_validates_acceptance_of_generates_message_with_custom_default_message - Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) - @topic.valid? - end - - # validates_presence_of w/ mocha - - def test_validates_presence_of_generates_message - Topic.validates_presence_of :title - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.valid? - end - - def test_validates_presence_of_generates_message_with_custom_default_message - Topic.validates_presence_of :title, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_length_of :within w/ mocha - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_length_of :is w/ mocha - - def test_validates_length_of_is_generates_message - Topic.validates_length_of :title, :is => 5 - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_is_generates_message_with_custom_default_message - Topic.validates_length_of :title, :is => 5, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_format_of w/ mocha - - def test_validates_format_of_generates_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) - @topic.valid? - end - - def test_validates_format_of_generates_message_with_custom_default_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) - @topic.valid? - end - - # validates_inclusion_of w/ mocha - - def test_validates_inclusion_of_generates_message - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) - @topic.valid? - end - - def test_validates_inclusion_of_generates_message_with_custom_default_message - Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) - @topic.valid? - end - - # validates_exclusion_of w/ mocha - - def test_validates_exclusion_of_generates_message - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_exclusion_of_generates_message_with_custom_default_message - Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of without :only_integer w/ mocha - - def test_validates_numericality_of_generates_message - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of with :only_integer w/ mocha - - def test_validates_numericality_of_only_integer_generates_message - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of :odd w/ mocha - - def test_validates_numericality_of_odd_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of :less_than w/ mocha - - def test_validates_numericality_of_less_than_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) - @topic.valid? - end - - # validates_confirmation_of w/o mocha - - def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_acceptance_of w/o mocha - - def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_presence_of w/o mocha - - def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_length_of :within w/o mocha - - def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_length_of :is w/o mocha - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - - # validates_format_of w/o mocha - - def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_inclusion_of w/o mocha - - def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_exclusion_of w/o mocha - - def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of without :only_integer w/o mocha - - def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of with :only_integer w/o mocha - - def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of :odd w/o mocha - - def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of :less_than w/o mocha - - def test_validates_numericality_of_less_than_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_string - Topic.validates_presence_of :title, :message => "I am a custom error" - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end -end - -class ActiveRecordValidationsGenerateMessageI18nTests < ActiveRecord::TestCase - - def setup - @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :messages => { - :inclusion => "is not included in the list", - :exclusion => "is reserved", - :invalid => "is invalid", - :confirmation => "doesn't match confirmation", - :accepted => "must be accepted", - :empty => "can't be empty", - :blank => "can't be blank", - :too_long => "is too long (maximum is {{count}} characters)", - :too_short => "is too short (minimum is {{count}} characters)", - :wrong_length => "is the wrong length (should be {{count}} characters)", - :taken => "has already been taken", - :not_a_number => "is not a number", - :greater_than => "must be greater than {{count}}", - :greater_than_or_equal_to => "must be greater than or equal to {{count}}", - :equal_to => "must be equal to {{count}}", - :less_than => "must be less than {{count}}", - :less_than_or_equal_to => "must be less than or equal to {{count}}", - :odd => "must be odd", - :even => "must be even" - } - } - } - } - end - - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') - end - - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') - end - - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') - end - - def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) - end - - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') - end - - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) - end - - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') - end - - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) - end - - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') - end - - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) - end - - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') - end - - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) - end - - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end - - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) - end - - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) - end - - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) - def test_generate_message_taken_with_default_message - assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') - end - - def test_generate_message_taken_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') - end - - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) - end - # ActiveRecord#RecordInvalid exception - - test "RecordInvalid exception can be localized" do - topic = Topic.new - topic.errors.add(:title, :invalid) - topic.errors.add(:title, :blank) - assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message - end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 5cdb623eef..130231c622 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -148,40 +148,9 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal "100,000", d.salary_before_type_cast end - def test_validates_length_with_globally_modified_error_message - defaults = ActiveSupport::Deprecation.silence { ActiveRecord::Errors.default_error_messages } - original_message = defaults[:too_short] - defaults[:too_short] = 'tu est trops petit hombre {{count}}' - - Topic.validates_length_of :title, :minimum => 10 - t = Topic.create(:title => 'too short') - assert !t.valid? - - assert_equal ['tu est trops petit hombre 10'], t.errors[:title] - - ensure - defaults[:too_short] = original_message - end - def test_validates_acceptance_of_as_database_column Topic.validates_acceptance_of(:author_name) topic = Topic.create("author_name" => "Dan Brown") assert_equal "Dan Brown", topic["author_name"] end - - def test_deprecated_validation_instance_methods - tom = DeprecatedPerson.new - - assert_deprecated do - assert tom.invalid? - assert_equal ["always invalid", "invalid on create"], tom.errors[:name] - end - - tom.save(false) - - assert_deprecated do - assert tom.invalid? - assert_equal ["always invalid", "invalid on update"], tom.errors[:name] - end - end end diff --git a/activeresource/Rakefile b/activeresource/Rakefile index c87345e3b5..834d4c0c59 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -62,8 +62,6 @@ spec = eval(File.read('activeresource.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do @@ -80,10 +78,10 @@ task :lines do codelines += 1 end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - + total_lines += lines total_codelines += codelines - + lines, codelines = 0, 0 end @@ -94,14 +92,14 @@ end # Publishing ------------------------------------------------------ desc "Publish the beta gem" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 6a33040243..ee12f785b5 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,9 +1,15 @@ +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/activeresource/lib") +end + require 'rubygems' require 'test/unit' require 'active_support' require 'active_support/test_case' - -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_resource' $:.unshift "#{File.dirname(__FILE__)}/../test" diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 9c5803c52a..4edeadf10c 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,9 @@ *Edge* +* Update Edinburgh TimeZone to use "Europe/London" instead of "Europe/Dublin" #3310 [Phil Ross] + +* Update bundled TZInfo to v0.3.15 [Geoff Buesing] + * JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON library agnostic. [Jeremy Kemper] * String #to_time and #to_datetime: handle fractional seconds #864 [Jason Frey] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index c27167287d..2ada91830f 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -49,8 +49,6 @@ spec = eval(File.read('activesupport.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end desc "Publish the beta gem" @@ -61,7 +59,7 @@ task :pgem => [:package] do end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload end @@ -88,39 +86,39 @@ namespace :tzinfo do Rake::Task['tzinfo:cleanup_tmp'].invoke puts <<-EOV *** FINAL TZINFO BUNDLING STEPS *** - + 1. Update TZInfo version in lib/active_support/vendor.rb 2. gem uninstall tzinfo on local system before running tests, to ensure tests are running against bundled version - + If a test fails because a particular zone can't be found, it's likely because the TZInfo identifier in the ActiveSupport::TimeZone::MAPPING hash is referencing a linked timezone instead of referencing the timezone directly. In this case, just change the MAPPING value to the correct identifier, and unpack TZInfo again. EOV end - + task :unpack_gem do mkdir_p "tmp" cd "tmp" sh "gem unpack --version #{ENV['VERSION'] || "'> 0'"} tzinfo" cd ".." end - + task :copy_classes => :unpack_gem do - mkdir_p "#{destination_path}/tzinfo" - cp "#{tmp_path}/lib/tzinfo.rb", destination_path - comment_requires_for_excluded_classes!('tzinfo.rb') + mkdir_p "#{destination_path}/lib/tzinfo" + cp "#{tmp_path}/lib/tzinfo.rb", "#{destination_path}/lib" + comment_requires_for_excluded_classes!('lib/tzinfo.rb') files = FileList["#{tmp_path}/lib/tzinfo/*.rb"] files.each do |file| filename = File.basename(file) unless excluded_classes.include? filename.sub(/.rb$/, '') - cp "#{tmp_path}/lib/tzinfo/#{filename}", "#{destination_path}/tzinfo" - comment_requires_for_excluded_classes!("tzinfo/#{filename}") + cp "#{tmp_path}/lib/tzinfo/#{filename}", "#{destination_path}/lib/tzinfo" + comment_requires_for_excluded_classes!("lib/tzinfo/#{filename}") end end end - + task :copy_definitions => :unpack_gem do - definitions_path = "#{destination_path}/tzinfo/definitions/" + definitions_path = "#{destination_path}/lib/tzinfo/definitions/" mkdir_p definitions_path ActiveSupport::TimeZone::MAPPING.values.each do |zone| subdir = nil @@ -135,11 +133,11 @@ namespace :tzinfo do task :cleanup_tmp do rm_rf "tmp" end - + def comment_requires_for_excluded_classes!(file) lines = open("#{destination_path}/#{file}") {|f| f.readlines} updated = false - + new_lines = [] lines.each do |line| if Regexp.new("require 'tzinfo/(#{excluded_classes.join('|')})'") === line @@ -149,29 +147,29 @@ namespace :tzinfo do new_lines << line end end - + if updated open("#{destination_path}/#{file}", "w") {|f| f.write(new_lines.join)} end end - + def version ENV['VERSION'] ||= get_unpacked_version end - + def get_unpacked_version m = (FileList["tmp/tzinfo-*"].to_s.match /\d+\.\d+\.\d+/) m ? m[0] : raise(LoadError, "TZInfo gem must be installed locally. `gem install tzinfo` and try again") end - + def tmp_path "tmp/tzinfo-#{version}" end - + def destination_path "lib/active_support/vendor/tzinfo-#{version}" end - + def excluded_classes %w(country country_index_definition country_info country_timezone timezone_index_definition timezone_proxy tzdataparser) end diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb index a0fc2bb123..63f7338a68 100644 --- a/activesupport/lib/active_support/autoload.rb +++ b/activesupport/lib/active_support/autoload.rb @@ -2,6 +2,7 @@ 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' @@ -17,9 +18,9 @@ module ActiveSupport autoload :MessageVerifier, 'active_support/message_verifier' autoload :Multibyte, 'active_support/multibyte' autoload :OptionMerger, 'active_support/option_merger' - autoload :Orchestra, 'active_support/orchestra' 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' diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb new file mode 100644 index 0000000000..6a41aab166 --- /dev/null +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -0,0 +1,59 @@ +require 'active_support/core_ext/benchmark' + +module ActiveSupport + module Benchmarkable + # Allows you to measure the execution time of a block + # in a template and records the result to the log. Wrap this block around + # expensive operations or possible bottlenecks to get a time reading + # for the operation. For example, let's say you thought your file + # processing method was taking too long; you could wrap it in a benchmark block. + # + # <% benchmark "Process data files" do %> + # <%= expensive_files_operation %> + # <% end %> + # + # That would add something like "Process data files (345.2ms)" to the log, + # which you can then use to compare timings when optimizing your code. + # + # You may give an optional logger level as the :level option. + # (:debug, :info, :warn, :error); the default value is :info. + # + # <% benchmark "Low-level files", :level => :debug do %> + # <%= lowlevel_files_operation %> + # <% end %> + # + # Finally, you can pass true as the third argument to silence all log activity + # inside the block. This is great for boiling down a noisy block to just a single statement: + # + # <% benchmark "Process data files", :level => :info, :silence => true do %> + # <%= expensive_and_chatty_files_operation %> + # <% end %> + def benchmark(message = "Benchmarking", options = {}) + if logger + if options.is_a?(Symbol) + ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller) + options = { :level => options, :silence => false } + else + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + end + + result = nil + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) + result + else + yield + end + end + + # Silence the logger during the execution of the block. + # + def silence + old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger + yield + ensure + logger.level = old_logger_level if logger + end + end +end diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index dfce507b33..29c3843d16 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -53,7 +53,6 @@ module ActiveSupport FileUtils.mkdir_p(File.dirname(log)) @log = open(log, (File::WRONLY | File::APPEND | File::CREAT)) @log.sync = true - @log.write("# Logfile created on %s" % [Time.now.to_s]) end end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index a415686020..818983fdd6 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -105,7 +105,7 @@ module ActiveSupport # cache.write("city", "Duckburgh") # cache.read("city") # => "Duckburgh" class Store - cattr_accessor :logger + cattr_accessor :logger, :instance_writter => false attr_reader :silence alias :silence? :silence @@ -122,6 +122,15 @@ module ActiveSupport @silence = previous_silence end + # Set to true if cache stores should be instrumented. By default is false. + def self.instrument=(boolean) + Thread.current[:instrument_cache_store] = boolean + end + + def self.instrument + Thread.current[:instrument_cache_store] || false + end + # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # @@ -219,7 +228,6 @@ module ActiveSupport end def increment(key, amount = 1) - log("incrementing", key, amount) if num = read(key) write(key, num + amount) else @@ -228,7 +236,6 @@ module ActiveSupport end def decrement(key, amount = 1) - log("decrementing", key, amount) if num = read(key) write(key, num - amount) else @@ -244,16 +251,20 @@ module ActiveSupport end def instrument(operation, key, options, &block) - payload = { :key => key } - payload.merge!(options) if options.is_a?(Hash) + log(operation, key, options) - event = ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block) - log("#{operation} (%.1fms)" % event.duration, key, options) - event.result + if self.class.instrument + payload = { :key => key } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument(:"cache_#{operation}", payload, &block) + else + yield + end end def log(operation, key, options) - logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence? + return unless logger && !silence? + logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") end end end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 516af99ce9..bec9de86ed 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -103,17 +103,20 @@ module ActiveSupport end def increment(key, amount = 1) # :nodoc: - log("incrementing", key, amount) + response = instrument(:increment, key, :amount => amount) do + @data.incr(key, amount) + end - response = @data.incr(key, amount) response == Response::NOT_FOUND ? nil : response rescue MemCache::MemCacheError nil end def decrement(key, amount = 1) # :nodoc: - log("decrement", key, amount) - response = @data.decr(key, amount) + response = instrument(:decrement, key, :amount => amount) do + @data.decr(key, amount) + end + response == Response::NOT_FOUND ? nil : response rescue MemCache::MemCacheError nil diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 9d45c2739b..38bb68c1ec 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -2,17 +2,13 @@ class Array # Wraps the object in an Array unless it's an Array. Converts the # object to an Array using #to_ary if it implements that. def self.wrap(object) - case object - when nil + if object.nil? [] - when self - object + # to_a doesn't work correctly with Array() but to_ary always does + elsif object.respond_to?(:to_a) && !object.respond_to?(:to_ary) + [object] else - if object.respond_to?(:to_ary) - object.to_ary - else - [object] - end + Array(object) end end end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 6c67df7f50..301c09fc73 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,39 +1,57 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/array/extract_options' class Class def superclass_delegating_reader(*names) - class_name_to_stop_searching_on = superclass.name.blank? ? "Object" : superclass.name + class_to_stop_searching_on = superclass.name.blank? ? "Object" : superclass.name + options = names.extract_options! + names.each do |name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def self.#{name} # def self.only_reader - if defined?(@#{name}) # if defined?(@only_reader) - @#{name} # @only_reader - elsif superclass < #{class_name_to_stop_searching_on} && # elsif superclass < Object && - superclass.respond_to?(:#{name}) # superclass.respond_to?(:only_reader) - superclass.#{name} # superclass.only_reader - end # end - end # end - def #{name} # def only_reader - self.class.#{name} # self.class.only_reader - end # end - def self.#{name}? # def self.only_reader? - !!#{name} # !!only_reader - end # end - def #{name}? # def only_reader? - !!#{name} # !!only_reader - end # end + # def self.only_reader + # if defined?(@only_reader) + # @only_reader + # elsif superclass < Object && superclass.respond_to?(:only_reader) + # superclass.only_reader + # end + # end + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def self.#{name} + if defined?(@#{name}) + @#{name} + elsif superclass < #{class_to_stop_searching_on} && superclass.respond_to?(:#{name}) + superclass.#{name} + end + end EOS + + unless options[:instance_reader] == false + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def #{name} # def only_reader + self.class.#{name} # self.class.only_reader + end # end + def self.#{name}? # def self.only_reader? + !!#{name} # !!only_reader + end # end + def #{name}? # def only_reader? + !!#{name} # !!only_reader + end # end + EOS + end end end def superclass_delegating_writer(*names, &block) + options = names.extract_options! + names.each do |name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) + class_eval <<-EOS, __FILE__, __LINE__ + 1 def self.#{name}=(value) # def self.property=(value) @#{name} = value # @property = value end # end EOS - self.send("#{name}=", yield) if block_given? + + self.send(:"#{name}=", yield) if block_given? end end diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index 8bac2dff19..e4d22516c1 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/array/extract_options' # Retain for backward compatibility. Methods are now included in Class. module ClassInheritableAttributes # :nodoc: @@ -10,16 +11,19 @@ end # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy. class Class # :nodoc: def class_inheritable_reader(*syms) + options = syms.extract_options! syms.each do |sym| next if sym.is_a?(Hash) class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def self.#{sym} # def self.after_add - read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add) - end # end - - def #{sym} # def after_add - self.class.#{sym} # self.class.after_add - end # end + def self.#{sym} # def self.after_add + read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add) + end # end + # + #{" # + def #{sym} # def after_add + self.class.#{sym} # self.class.after_add + end # end + " unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false EOS end end @@ -156,7 +160,7 @@ class Class # moving on). In particular, this makes the return value of this function # less useful. def extlib_inheritable_reader(*ivars) - instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash) + options = ivars.extract_options! ivars.each do |ivar| self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -164,10 +168,10 @@ class Class return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) ivar = superclass.#{ivar} return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") - @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar + @#{ivar} = ivar.duplicable? ? ivar.dup : ivar end RUBY - unless instance_reader == false + unless options[:instance_reader] == false self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{ivar} self.class.#{ivar} @@ -190,14 +194,15 @@ class Class # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. def extlib_inheritable_writer(*ivars) - instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash) + options = ivars.extract_options! + ivars.each do |ivar| self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{ivar}=(obj) @#{ivar} = obj end RUBY - unless instance_writer == false + unless options[:instance_writer] == false self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{ivar}=(obj) self.class.#{ivar} = obj end RUBY diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 7aa394d7bf..e4a864c20f 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -29,4 +29,10 @@ class Hash replace(hash) omit end + + def extract!(*keys) + result = {} + keys.each {|key| result[key] = delete(key) } + result + end end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 1722726ca2..a2d4d50076 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,6 +1,6 @@ class Object # Can you safely .dup this object? - # False for nil, false, true, symbols, numbers, and class objects; true otherwise. + # False for nil, false, true, symbols, numbers, class and module objects; true otherwise. def duplicable? true end @@ -41,3 +41,9 @@ class Class #:nodoc: false end end + +class Module #:nodoc: + def duplicable? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index 95d06ee6ee..784145f5fb 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -1,27 +1,5 @@ class Regexp #:nodoc: - def number_of_captures - Regexp.new("|#{source}").match('').captures.length - end - def multiline? options & MULTILINE == MULTILINE end - - class << self - def optionalize(pattern) - return pattern if pattern == "" - - case unoptionalize(pattern) - when /\A(.|\(.*\))\Z/ then "#{pattern}?" - else "(?:#{pattern})?" - end - end - - def unoptionalize(pattern) - [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| - return $1 if regexp =~ pattern - end - return pattern - end - end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb new file mode 100644 index 0000000000..9eae3bebe2 --- /dev/null +++ b/activesupport/lib/active_support/notifications.rb @@ -0,0 +1,191 @@ +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 + # action in Ruby you just need to do: + # + # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # render :text => "Foo" + # end + # + # You can consume those events and the information they provide by registering + # a subscriber. For instance, let's store all instrumented events in an array: + # + # @events = [] + # + # ActiveSupport::Notifications.subscribe do |*args| + # @events << ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # render :text => "Foo" + # end + # + # event = @events.first + # event.name #=> :render + # event.duration #=> 10 (in miliseconds) + # event.result #=> "Foo" + # event.payload #=> { :extra => :information } + # + # When subscribing to Notifications, you can pass a pattern, to only consume + # events that match the pattern: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @render_events << event + # end + # + # Notifications ships with a queue implementation that consumes and publish events + # to subscribers in a thread. You can use any queue implementation you want. + # + module Notifications + mattr_accessor :queue + + class << self + delegate :instrument, :transaction_id, :transaction, :to => :instrumenter + + def instrumenter + Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher) + end + + def publisher + @publisher ||= Publisher.new(queue) + end + + def subscribe(pattern=nil, &block) + Subscriber.new(queue).bind(pattern).subscribe(&block) + 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) + @queue = queue + end + + def publish(*args) + @queue.publish(*args) + end + end + + class Subscriber + def initialize(queue) + @queue = queue + end + + def bind(pattern) + @pattern = pattern + self + end + + def subscribe + @queue.subscribe(@pattern) do |*args| + yield *args + end + 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 + + # 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 = [] + @stream = Queue.new + Thread.new { consume } + end + + def publish(*args) + @stream.push(args) + end + + def subscribe(pattern=nil, &block) + @listeners << Listener.new(pattern, &block) + end + + def consume + while args = @stream.shift + @listeners.each { |l| l.publish(*args) } + end + end + + class Listener + # attr_reader :thread + + 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 + end + end + end + + Notifications.queue = Notifications::LittleFanout.new +end diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb deleted file mode 100644 index efe30669d8..0000000000 --- a/activesupport/lib/active_support/orchestra.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'thread' - -module ActiveSupport - # Orchestra provides an instrumentation API for Ruby. To instrument an action - # in Ruby you just need to: - # - # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do - # render :text => "Foo" - # end - # - # Those actions are consumed by listeners. A listener is anything that responds - # to push. You can even register an array: - # - # @listener = [] - # ActiveSupport::Orchestra.register @listener - # - # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do - # render :text => "Foo" - # end - # - # event #=> ActiveSupport::Orchestra::Event - # event.name #=> :render - # event.duration #=> 10 (in miliseconds) - # event.result #=> "Foo" - # event.payload #=> { :extra => :information } - # - # Orchestra ships with a default listener implementation which puts events in - # a stream and consume them in a Thread. This implementation is thread safe - # and is available at ActiveSupport::Orchestra::Listener. - # - module Orchestra - @stacked_events = Hash.new { |h,k| h[k] = [] } - @listeners = [] - - def self.instrument(name, payload=nil) - stack = @stacked_events[Thread.current.object_id] - event = Event.new(name, stack.last, payload) - stack << event - event.result = yield - event - ensure - event.finish! - stack.delete(event) - @listeners.each { |s| s.push(event) } - end - - def self.register(listener) - @listeners << listener - end - - def self.unregister(listener) - @listeners.delete(listener) - end - - class Event - attr_reader :name, :time, :duration, :parent, :thread_id, :payload - attr_accessor :result - - def initialize(name, parent=nil, payload=nil) - @name = name - @time = Time.now - @thread_id = Thread.current.object_id - @parent = parent - @payload = payload - end - - def finish! - @duration = 1000 * (Time.now.to_f - @time.to_f) - end - end - - class Listener - attr_reader :mutex, :signaler, :thread - - def initialize - @mutex, @signaler = Mutex.new, ConditionVariable.new - @stream = [] - @thread = Thread.new do - loop do - (event = @stream.shift) ? consume(event) : wait - end - end - end - - def wait - @mutex.synchronize do - @signaler.wait(@mutex) - end - end - - def push(event) - @mutex.synchronize do - @stream.push(event) - @signaler.broadcast - end - end - - def consume(event) - raise NotImplementedError - end - end - end -end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 564528bfe2..53a4c7acf5 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -63,7 +63,7 @@ module ActiveSupport "Azores" => "Atlantic/Azores", "Cape Verde Is." => "Atlantic/Cape_Verde", "Dublin" => "Europe/Dublin", - "Edinburgh" => "Europe/Dublin", + "Edinburgh" => "Europe/London", "Lisbon" => "Europe/Lisbon", "London" => "Europe/London", "Casablanca" => "Africa/Casablanca", diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index 9f464c8246..4a711b7e25 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -1,8 +1,10 @@ +require 'pathname' + 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.13)].each do |lib, version| +[%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| # If the lib is not already requirable unless ActiveSupport.requirable? lib # Try to activate a gem ~> satisfying the requested version first. @@ -10,7 +12,8 @@ end gem lib, "~> #{version}" # Use the vendored lib if the gem's missing or we aren't using RubyGems. rescue LoadError, NoMethodError - $LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/vendor/#{lib}-#{version}/lib") + # There could be symlinks + $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.join("vendor/#{lib}-#{version}/lib").realpath.to_s end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb deleted file mode 100644 index 8f4dd31dbb..0000000000 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'tzinfo/timezone_definition' - -module TZInfo - module Definitions - module America - module Argentina - module Buenos_Aires - include TimezoneDefinition - - timezone 'America/Argentina/Buenos_Aires' do |tz| - tz.offset :o0, -14028, 0, :LMT - tz.offset :o1, -15408, 0, :CMT - tz.offset :o2, -14400, 0, :ART - tz.offset :o3, -14400, 3600, :ARST - tz.offset :o4, -10800, 0, :ART - tz.offset :o5, -10800, 3600, :ARST - - tz.transition 1894, 10, :o1, 17374555169, 7200 - tz.transition 1920, 5, :o2, 1453467407, 600 - tz.transition 1930, 12, :o3, 7278935, 3 - tz.transition 1931, 4, :o2, 19411461, 8 - tz.transition 1931, 10, :o3, 7279889, 3 - tz.transition 1932, 3, :o2, 19414141, 8 - tz.transition 1932, 11, :o3, 7281038, 3 - tz.transition 1933, 3, :o2, 19417061, 8 - tz.transition 1933, 11, :o3, 7282133, 3 - tz.transition 1934, 3, :o2, 19419981, 8 - tz.transition 1934, 11, :o3, 7283228, 3 - tz.transition 1935, 3, :o2, 19422901, 8 - tz.transition 1935, 11, :o3, 7284323, 3 - tz.transition 1936, 3, :o2, 19425829, 8 - tz.transition 1936, 11, :o3, 7285421, 3 - tz.transition 1937, 3, :o2, 19428749, 8 - tz.transition 1937, 11, :o3, 7286516, 3 - tz.transition 1938, 3, :o2, 19431669, 8 - tz.transition 1938, 11, :o3, 7287611, 3 - tz.transition 1939, 3, :o2, 19434589, 8 - tz.transition 1939, 11, :o3, 7288706, 3 - tz.transition 1940, 3, :o2, 19437517, 8 - tz.transition 1940, 7, :o3, 7289435, 3 - tz.transition 1941, 6, :o2, 19441285, 8 - tz.transition 1941, 10, :o3, 7290848, 3 - tz.transition 1943, 8, :o2, 19447501, 8 - tz.transition 1943, 10, :o3, 7293038, 3 - tz.transition 1946, 3, :o2, 19455045, 8 - tz.transition 1946, 10, :o3, 7296284, 3 - tz.transition 1963, 10, :o2, 19506429, 8 - tz.transition 1963, 12, :o3, 7315136, 3 - tz.transition 1964, 3, :o2, 19507645, 8 - tz.transition 1964, 10, :o3, 7316051, 3 - tz.transition 1965, 3, :o2, 19510565, 8 - tz.transition 1965, 10, :o3, 7317146, 3 - tz.transition 1966, 3, :o2, 19513485, 8 - tz.transition 1966, 10, :o3, 7318241, 3 - tz.transition 1967, 4, :o2, 19516661, 8 - tz.transition 1967, 10, :o3, 7319294, 3 - tz.transition 1968, 4, :o2, 19519629, 8 - tz.transition 1968, 10, :o3, 7320407, 3 - tz.transition 1969, 4, :o2, 19522541, 8 - tz.transition 1969, 10, :o4, 7321499, 3 - tz.transition 1974, 1, :o5, 128142000 - tz.transition 1974, 5, :o4, 136605600 - tz.transition 1988, 12, :o5, 596948400 - tz.transition 1989, 3, :o4, 605066400 - tz.transition 1989, 10, :o5, 624423600 - tz.transition 1990, 3, :o4, 636516000 - tz.transition 1990, 10, :o5, 656478000 - tz.transition 1991, 3, :o4, 667965600 - tz.transition 1991, 10, :o5, 687927600 - tz.transition 1992, 3, :o4, 699415200 - tz.transition 1992, 10, :o5, 719377200 - tz.transition 1993, 3, :o4, 731469600 - tz.transition 1999, 10, :o3, 938919600 - tz.transition 2000, 3, :o4, 952052400 - tz.transition 2007, 12, :o5, 1198983600 - tz.transition 2008, 3, :o4, 1205632800 - tz.transition 2008, 10, :o5, 1224385200 - tz.transition 2009, 3, :o4, 1237082400 - tz.transition 2009, 10, :o5, 1255834800 - tz.transition 2010, 3, :o4, 1269136800 - tz.transition 2010, 10, :o5, 1287284400 - tz.transition 2011, 3, :o4, 1300586400 - tz.transition 2011, 10, :o5, 1318734000 - tz.transition 2012, 3, :o4, 1332036000 - tz.transition 2012, 10, :o5, 1350788400 - tz.transition 2013, 3, :o4, 1363485600 - tz.transition 2013, 10, :o5, 1382238000 - tz.transition 2014, 3, :o4, 1394935200 - tz.transition 2014, 10, :o5, 1413687600 - tz.transition 2015, 3, :o4, 1426384800 - tz.transition 2015, 10, :o5, 1445137200 - tz.transition 2016, 3, :o4, 1458439200 - tz.transition 2016, 10, :o5, 1476586800 - tz.transition 2017, 3, :o4, 1489888800 - tz.transition 2017, 10, :o5, 1508036400 - tz.transition 2018, 3, :o4, 1521338400 - tz.transition 2018, 10, :o5, 1540090800 - tz.transition 2019, 3, :o4, 1552788000 - tz.transition 2019, 10, :o5, 1571540400 - tz.transition 2020, 3, :o4, 1584237600 - tz.transition 2020, 10, :o5, 1602990000 - tz.transition 2021, 3, :o4, 1616292000 - tz.transition 2021, 10, :o5, 1634439600 - tz.transition 2022, 3, :o4, 1647741600 - tz.transition 2022, 10, :o5, 1665889200 - tz.transition 2023, 3, :o4, 1679191200 - tz.transition 2023, 10, :o5, 1697338800 - tz.transition 2024, 3, :o4, 1710640800 - tz.transition 2024, 10, :o5, 1729393200 - tz.transition 2025, 3, :o4, 1742090400 - tz.transition 2025, 10, :o5, 1760842800 - tz.transition 2026, 3, :o4, 1773540000 - tz.transition 2026, 10, :o5, 1792292400 - tz.transition 2027, 3, :o4, 1805594400 - tz.transition 2027, 10, :o5, 1823742000 - tz.transition 2028, 3, :o4, 1837044000 - tz.transition 2028, 10, :o5, 1855191600 - tz.transition 2029, 3, :o4, 1868493600 - tz.transition 2029, 10, :o5, 1887246000 - tz.transition 2030, 3, :o4, 1899943200 - tz.transition 2030, 10, :o5, 1918695600 - tz.transition 2031, 3, :o4, 1931392800 - tz.transition 2031, 10, :o5, 1950145200 - tz.transition 2032, 3, :o4, 1963447200 - tz.transition 2032, 10, :o5, 1981594800 - tz.transition 2033, 3, :o4, 1994896800 - tz.transition 2033, 10, :o5, 2013044400 - tz.transition 2034, 3, :o4, 2026346400 - tz.transition 2034, 10, :o5, 2044494000 - tz.transition 2035, 3, :o4, 2057796000 - tz.transition 2035, 10, :o5, 2076548400 - tz.transition 2036, 3, :o4, 2089245600 - tz.transition 2036, 10, :o5, 2107998000 - tz.transition 2037, 3, :o4, 2120695200 - tz.transition 2037, 10, :o5, 2139447600 - tz.transition 2038, 3, :o4, 29586043, 12 - tz.transition 2038, 10, :o5, 19725709, 8 - tz.transition 2039, 3, :o4, 29590411, 12 - tz.transition 2039, 10, :o5, 19728621, 8 - tz.transition 2040, 3, :o4, 29594779, 12 - tz.transition 2040, 10, :o5, 19731589, 8 - tz.transition 2041, 3, :o4, 29599147, 12 - tz.transition 2041, 10, :o5, 19734501, 8 - tz.transition 2042, 3, :o4, 29603515, 12 - tz.transition 2042, 10, :o5, 19737413, 8 - tz.transition 2043, 3, :o4, 29607883, 12 - tz.transition 2043, 10, :o5, 19740325, 8 - tz.transition 2044, 3, :o4, 29612335, 12 - tz.transition 2044, 10, :o5, 19743237, 8 - tz.transition 2045, 3, :o4, 29616703, 12 - tz.transition 2045, 10, :o5, 19746149, 8 - tz.transition 2046, 3, :o4, 29621071, 12 - tz.transition 2046, 10, :o5, 19749117, 8 - tz.transition 2047, 3, :o4, 29625439, 12 - tz.transition 2047, 10, :o5, 19752029, 8 - tz.transition 2048, 3, :o4, 29629807, 12 - tz.transition 2048, 10, :o5, 19754941, 8 - tz.transition 2049, 3, :o4, 29634259, 12 - tz.transition 2049, 10, :o5, 19757853, 8 - tz.transition 2050, 3, :o4, 29638627, 12 - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb deleted file mode 100644 index 4f187b4ad4..0000000000 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'tzinfo/timezone_definition' - -module TZInfo - module Definitions - module Asia - module Karachi - include TimezoneDefinition - - timezone 'Asia/Karachi' do |tz| - tz.offset :o0, 16092, 0, :LMT - tz.offset :o1, 19800, 0, :IST - tz.offset :o2, 19800, 3600, :IST - tz.offset :o3, 18000, 0, :KART - tz.offset :o4, 18000, 0, :PKT - tz.offset :o5, 18000, 3600, :PKST - - tz.transition 1906, 12, :o1, 1934061051, 800 - tz.transition 1942, 8, :o2, 116668957, 48 - tz.transition 1945, 10, :o1, 116723675, 48 - tz.transition 1951, 9, :o3, 116828125, 48 - tz.transition 1971, 3, :o4, 38775600 - tz.transition 2002, 4, :o5, 1018119660 - tz.transition 2002, 10, :o4, 1033840860 - tz.transition 2008, 5, :o5, 1212260400 - tz.transition 2008, 10, :o4, 1225476000 - tz.transition 2009, 4, :o5, 1239735600 - tz.transition 2009, 10, :o4, 1257012000 - end - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo.rb index c8bdbeec5d..c8bdbeec5d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone.rb index 5eccbdf0db..5eccbdf0db 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone_info.rb index a45d94554b..a45d94554b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Algiers.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Algiers.rb index 8c5f25577f..8c5f25577f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Algiers.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Algiers.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Cairo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Cairo.rb index 6e6daf3522..b7ed8e8244 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Cairo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Cairo.rb @@ -129,89 +129,89 @@ module TZInfo tz.transition 2008, 4, :o2, 1209074400 tz.transition 2008, 8, :o1, 1219957200 tz.transition 2009, 4, :o2, 1240524000 - tz.transition 2009, 8, :o1, 1251406800 + tz.transition 2009, 8, :o1, 1250802000 tz.transition 2010, 4, :o2, 1272578400 - tz.transition 2010, 8, :o1, 1282856400 + tz.transition 2010, 9, :o1, 1285880400 tz.transition 2011, 4, :o2, 1304028000 - tz.transition 2011, 8, :o1, 1314306000 + tz.transition 2011, 9, :o1, 1317330000 tz.transition 2012, 4, :o2, 1335477600 - tz.transition 2012, 8, :o1, 1346360400 + tz.transition 2012, 9, :o1, 1348779600 tz.transition 2013, 4, :o2, 1366927200 - tz.transition 2013, 8, :o1, 1377810000 + tz.transition 2013, 9, :o1, 1380229200 tz.transition 2014, 4, :o2, 1398376800 - tz.transition 2014, 8, :o1, 1409259600 + tz.transition 2014, 9, :o1, 1411678800 tz.transition 2015, 4, :o2, 1429826400 - tz.transition 2015, 8, :o1, 1440709200 + tz.transition 2015, 9, :o1, 1443128400 tz.transition 2016, 4, :o2, 1461880800 - tz.transition 2016, 8, :o1, 1472158800 + tz.transition 2016, 9, :o1, 1475182800 tz.transition 2017, 4, :o2, 1493330400 - tz.transition 2017, 8, :o1, 1504213200 + tz.transition 2017, 9, :o1, 1506632400 tz.transition 2018, 4, :o2, 1524780000 - tz.transition 2018, 8, :o1, 1535662800 + tz.transition 2018, 9, :o1, 1538082000 tz.transition 2019, 4, :o2, 1556229600 - tz.transition 2019, 8, :o1, 1567112400 + tz.transition 2019, 9, :o1, 1569531600 tz.transition 2020, 4, :o2, 1587679200 - tz.transition 2020, 8, :o1, 1598562000 + tz.transition 2020, 9, :o1, 1600981200 tz.transition 2021, 4, :o2, 1619733600 - tz.transition 2021, 8, :o1, 1630011600 + tz.transition 2021, 9, :o1, 1633035600 tz.transition 2022, 4, :o2, 1651183200 - tz.transition 2022, 8, :o1, 1661461200 + tz.transition 2022, 9, :o1, 1664485200 tz.transition 2023, 4, :o2, 1682632800 - tz.transition 2023, 8, :o1, 1693515600 + tz.transition 2023, 9, :o1, 1695934800 tz.transition 2024, 4, :o2, 1714082400 - tz.transition 2024, 8, :o1, 1724965200 + tz.transition 2024, 9, :o1, 1727384400 tz.transition 2025, 4, :o2, 1745532000 - tz.transition 2025, 8, :o1, 1756414800 + tz.transition 2025, 9, :o1, 1758834000 tz.transition 2026, 4, :o2, 1776981600 - tz.transition 2026, 8, :o1, 1787864400 + tz.transition 2026, 9, :o1, 1790283600 tz.transition 2027, 4, :o2, 1809036000 - tz.transition 2027, 8, :o1, 1819314000 + tz.transition 2027, 9, :o1, 1822338000 tz.transition 2028, 4, :o2, 1840485600 - tz.transition 2028, 8, :o1, 1851368400 + tz.transition 2028, 9, :o1, 1853787600 tz.transition 2029, 4, :o2, 1871935200 - tz.transition 2029, 8, :o1, 1882818000 + tz.transition 2029, 9, :o1, 1885237200 tz.transition 2030, 4, :o2, 1903384800 - tz.transition 2030, 8, :o1, 1914267600 + tz.transition 2030, 9, :o1, 1916686800 tz.transition 2031, 4, :o2, 1934834400 - tz.transition 2031, 8, :o1, 1945717200 + tz.transition 2031, 9, :o1, 1948136400 tz.transition 2032, 4, :o2, 1966888800 - tz.transition 2032, 8, :o1, 1977166800 + tz.transition 2032, 9, :o1, 1980190800 tz.transition 2033, 4, :o2, 1998338400 - tz.transition 2033, 8, :o1, 2008616400 + tz.transition 2033, 9, :o1, 2011640400 tz.transition 2034, 4, :o2, 2029788000 - tz.transition 2034, 8, :o1, 2040670800 + tz.transition 2034, 9, :o1, 2043090000 tz.transition 2035, 4, :o2, 2061237600 - tz.transition 2035, 8, :o1, 2072120400 + tz.transition 2035, 9, :o1, 2074539600 tz.transition 2036, 4, :o2, 2092687200 - tz.transition 2036, 8, :o1, 2103570000 + tz.transition 2036, 9, :o1, 2105989200 tz.transition 2037, 4, :o2, 2124136800 - tz.transition 2037, 8, :o1, 2135019600 + tz.transition 2037, 9, :o1, 2137438800 tz.transition 2038, 4, :o2, 29586521, 12 - tz.transition 2038, 8, :o1, 19725299, 8 + tz.transition 2038, 9, :o1, 19725579, 8 tz.transition 2039, 4, :o2, 29590889, 12 - tz.transition 2039, 8, :o1, 19728211, 8 + tz.transition 2039, 9, :o1, 19728491, 8 tz.transition 2040, 4, :o2, 29595257, 12 - tz.transition 2040, 8, :o1, 19731179, 8 + tz.transition 2040, 9, :o1, 19731403, 8 tz.transition 2041, 4, :o2, 29599625, 12 - tz.transition 2041, 8, :o1, 19734091, 8 + tz.transition 2041, 9, :o1, 19734315, 8 tz.transition 2042, 4, :o2, 29603993, 12 - tz.transition 2042, 8, :o1, 19737003, 8 + tz.transition 2042, 9, :o1, 19737227, 8 tz.transition 2043, 4, :o2, 29608361, 12 - tz.transition 2043, 8, :o1, 19739915, 8 + tz.transition 2043, 9, :o1, 19740139, 8 tz.transition 2044, 4, :o2, 29612813, 12 - tz.transition 2044, 8, :o1, 19742827, 8 + tz.transition 2044, 9, :o1, 19743107, 8 tz.transition 2045, 4, :o2, 29617181, 12 - tz.transition 2045, 8, :o1, 19745795, 8 + tz.transition 2045, 9, :o1, 19746019, 8 tz.transition 2046, 4, :o2, 29621549, 12 - tz.transition 2046, 8, :o1, 19748707, 8 + tz.transition 2046, 9, :o1, 19748931, 8 tz.transition 2047, 4, :o2, 29625917, 12 - tz.transition 2047, 8, :o1, 19751619, 8 + tz.transition 2047, 9, :o1, 19751843, 8 tz.transition 2048, 4, :o2, 29630285, 12 - tz.transition 2048, 8, :o1, 19754531, 8 + tz.transition 2048, 9, :o1, 19754755, 8 tz.transition 2049, 4, :o2, 29634737, 12 - tz.transition 2049, 8, :o1, 19757443, 8 + tz.transition 2049, 9, :o1, 19757723, 8 tz.transition 2050, 4, :o2, 29639105, 12 - tz.transition 2050, 8, :o1, 19760355, 8 + tz.transition 2050, 9, :o1, 19760635, 8 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Casablanca.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Casablanca.rb index 18d73c93a0..18d73c93a0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Casablanca.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Casablanca.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Harare.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Harare.rb index 070c95ae0f..070c95ae0f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Harare.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Harare.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Johannesburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Johannesburg.rb index f0af0d8e33..f0af0d8e33 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Johannesburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Johannesburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Monrovia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Monrovia.rb index 40e711fa44..40e711fa44 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Monrovia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Monrovia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Nairobi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Nairobi.rb index 7b0a2f43be..7b0a2f43be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Nairobi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Nairobi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb new file mode 100644 index 0000000000..307f9546de --- /dev/null +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb @@ -0,0 +1,84 @@ +require 'tzinfo/timezone_definition' + +module TZInfo + module Definitions + module America + module Argentina + module Buenos_Aires + include TimezoneDefinition + + timezone 'America/Argentina/Buenos_Aires' do |tz| + tz.offset :o0, -14028, 0, :LMT + tz.offset :o1, -15408, 0, :CMT + tz.offset :o2, -14400, 0, :ART + tz.offset :o3, -14400, 3600, :ARST + tz.offset :o4, -10800, 0, :ART + tz.offset :o5, -10800, 3600, :ARST + + tz.transition 1894, 10, :o1, 17374555169, 7200 + tz.transition 1920, 5, :o2, 1453467407, 600 + tz.transition 1930, 12, :o3, 7278935, 3 + tz.transition 1931, 4, :o2, 19411461, 8 + tz.transition 1931, 10, :o3, 7279889, 3 + tz.transition 1932, 3, :o2, 19414141, 8 + tz.transition 1932, 11, :o3, 7281038, 3 + tz.transition 1933, 3, :o2, 19417061, 8 + tz.transition 1933, 11, :o3, 7282133, 3 + tz.transition 1934, 3, :o2, 19419981, 8 + tz.transition 1934, 11, :o3, 7283228, 3 + tz.transition 1935, 3, :o2, 19422901, 8 + tz.transition 1935, 11, :o3, 7284323, 3 + tz.transition 1936, 3, :o2, 19425829, 8 + tz.transition 1936, 11, :o3, 7285421, 3 + tz.transition 1937, 3, :o2, 19428749, 8 + tz.transition 1937, 11, :o3, 7286516, 3 + tz.transition 1938, 3, :o2, 19431669, 8 + tz.transition 1938, 11, :o3, 7287611, 3 + tz.transition 1939, 3, :o2, 19434589, 8 + tz.transition 1939, 11, :o3, 7288706, 3 + tz.transition 1940, 3, :o2, 19437517, 8 + tz.transition 1940, 7, :o3, 7289435, 3 + tz.transition 1941, 6, :o2, 19441285, 8 + tz.transition 1941, 10, :o3, 7290848, 3 + tz.transition 1943, 8, :o2, 19447501, 8 + tz.transition 1943, 10, :o3, 7293038, 3 + tz.transition 1946, 3, :o2, 19455045, 8 + tz.transition 1946, 10, :o3, 7296284, 3 + tz.transition 1963, 10, :o2, 19506429, 8 + tz.transition 1963, 12, :o3, 7315136, 3 + tz.transition 1964, 3, :o2, 19507645, 8 + tz.transition 1964, 10, :o3, 7316051, 3 + tz.transition 1965, 3, :o2, 19510565, 8 + tz.transition 1965, 10, :o3, 7317146, 3 + tz.transition 1966, 3, :o2, 19513485, 8 + tz.transition 1966, 10, :o3, 7318241, 3 + tz.transition 1967, 4, :o2, 19516661, 8 + tz.transition 1967, 10, :o3, 7319294, 3 + tz.transition 1968, 4, :o2, 19519629, 8 + tz.transition 1968, 10, :o3, 7320407, 3 + tz.transition 1969, 4, :o2, 19522541, 8 + tz.transition 1969, 10, :o4, 7321499, 3 + tz.transition 1974, 1, :o5, 128142000 + tz.transition 1974, 5, :o4, 136605600 + tz.transition 1988, 12, :o5, 596948400 + tz.transition 1989, 3, :o4, 605066400 + tz.transition 1989, 10, :o5, 624423600 + tz.transition 1990, 3, :o4, 636516000 + tz.transition 1990, 10, :o5, 656478000 + tz.transition 1991, 3, :o4, 667965600 + tz.transition 1991, 10, :o5, 687927600 + tz.transition 1992, 3, :o4, 699415200 + tz.transition 1992, 10, :o5, 719377200 + tz.transition 1993, 3, :o4, 731469600 + tz.transition 1999, 10, :o3, 938919600 + tz.transition 2000, 3, :o4, 952052400 + tz.transition 2007, 12, :o5, 1198983600 + tz.transition 2008, 3, :o4, 1205632800 + tz.transition 2008, 10, :o5, 1224385200 + tz.transition 2009, 3, :o4, 1237082400 + end + end + end + end + end +end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/San_Juan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/San_Juan.rb index ba8be4705f..ba8be4705f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/San_Juan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/San_Juan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Bogota.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Bogota.rb index ef96435c6a..ef96435c6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Bogota.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Bogota.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Caracas.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Caracas.rb index 27392a540a..27392a540a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Caracas.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Caracas.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chicago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chicago.rb index 0996857cf0..0996857cf0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chicago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chicago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chihuahua.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chihuahua.rb index 1710b57c79..1710b57c79 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chihuahua.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chihuahua.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Denver.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Denver.rb index 1c1efb5ff3..1c1efb5ff3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Denver.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Denver.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Godthab.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Godthab.rb index 1e05518b0d..1e05518b0d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Godthab.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Godthab.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Guatemala.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Guatemala.rb index a2bf73401c..a2bf73401c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Guatemala.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Guatemala.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Halifax.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Halifax.rb index d25ae775b3..d25ae775b3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Halifax.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Halifax.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb index f1430f6c24..f1430f6c24 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Juneau.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Juneau.rb index f646f3f54a..f646f3f54a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Juneau.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Juneau.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/La_Paz.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/La_Paz.rb index 45c907899f..45c907899f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/La_Paz.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/La_Paz.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Lima.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Lima.rb index af68ac29f7..af68ac29f7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Lima.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Lima.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Los_Angeles.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Los_Angeles.rb index 16007fd675..16007fd675 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Los_Angeles.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Los_Angeles.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mazatlan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mazatlan.rb index ba9e6efcf1..ba9e6efcf1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mazatlan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mazatlan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mexico_City.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mexico_City.rb index 2347fce647..2347fce647 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mexico_City.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mexico_City.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Monterrey.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Monterrey.rb index 5816a9eab1..5816a9eab1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Monterrey.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Monterrey.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/New_York.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/New_York.rb index 7d802bd2de..7d802bd2de 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/New_York.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/New_York.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Phoenix.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Phoenix.rb index b514e0c0f9..b514e0c0f9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Phoenix.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Phoenix.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Regina.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Regina.rb index ebdb68814a..ebdb68814a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Regina.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Regina.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Santiago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Santiago.rb index 0287c9ebc4..0287c9ebc4 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Santiago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Santiago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Sao_Paulo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Sao_Paulo.rb index 0524f81c04..0524f81c04 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Sao_Paulo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Sao_Paulo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/St_Johns.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/St_Johns.rb index e4a3599d35..e4a3599d35 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/St_Johns.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/St_Johns.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Tijuana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Tijuana.rb index 423059da46..423059da46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Tijuana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Tijuana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Almaty.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Almaty.rb index 9ee18970f1..9ee18970f1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Almaty.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Almaty.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baghdad.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baghdad.rb index 774dca1587..774dca1587 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baghdad.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baghdad.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baku.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baku.rb index e86340ebfa..e86340ebfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baku.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baku.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Bangkok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Bangkok.rb index 139194e5e5..139194e5e5 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Bangkok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Bangkok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Chongqing.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Chongqing.rb index 8c94b4ba86..8c94b4ba86 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Chongqing.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Chongqing.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Colombo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Colombo.rb index f6531fa819..f6531fa819 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Colombo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Colombo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Dhaka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Dhaka.rb index ccd0265503..46dce9a0d0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Dhaka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Dhaka.rb @@ -13,6 +13,7 @@ module TZInfo tz.offset :o3, 19800, 0, :IST tz.offset :o4, 21600, 0, :DACT tz.offset :o5, 21600, 0, :BDT + tz.offset :o6, 21600, 3600, :BDST tz.transition 1889, 12, :o1, 2083422167, 864 tz.transition 1941, 9, :o2, 524937943, 216 @@ -20,6 +21,7 @@ module TZInfo tz.transition 1942, 8, :o2, 116668957, 48 tz.transition 1951, 9, :o4, 116828123, 48 tz.transition 1971, 3, :o5, 38772000 + tz.transition 2009, 6, :o6, 1245430800 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Hong_Kong.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Hong_Kong.rb index f1edd75ac8..f1edd75ac8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Hong_Kong.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Hong_Kong.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Irkutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Irkutsk.rb index 2d47d9580b..2d47d9580b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Irkutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Irkutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jakarta.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jakarta.rb index cc58fa173b..cc58fa173b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jakarta.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jakarta.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jerusalem.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jerusalem.rb index 9b737b899e..9b737b899e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jerusalem.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jerusalem.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kabul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kabul.rb index 669c09790a..669c09790a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kabul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kabul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kamchatka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kamchatka.rb index 2f1690b3a9..2f1690b3a9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kamchatka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kamchatka.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb new file mode 100644 index 0000000000..dfe02c5cf6 --- /dev/null +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb @@ -0,0 +1,114 @@ +require 'tzinfo/timezone_definition' + +module TZInfo + module Definitions + module Asia + module Karachi + include TimezoneDefinition + + timezone 'Asia/Karachi' do |tz| + tz.offset :o0, 16092, 0, :LMT + tz.offset :o1, 19800, 0, :IST + tz.offset :o2, 19800, 3600, :IST + tz.offset :o3, 18000, 0, :KART + tz.offset :o4, 18000, 0, :PKT + tz.offset :o5, 18000, 3600, :PKST + + tz.transition 1906, 12, :o1, 1934061051, 800 + tz.transition 1942, 8, :o2, 116668957, 48 + tz.transition 1945, 10, :o1, 116723675, 48 + tz.transition 1951, 9, :o3, 116828125, 48 + tz.transition 1971, 3, :o4, 38775600 + tz.transition 2002, 4, :o5, 1018119660 + tz.transition 2002, 10, :o4, 1033840860 + tz.transition 2008, 5, :o5, 1212260400 + tz.transition 2008, 10, :o4, 1225476000 + tz.transition 2009, 4, :o5, 1239735600 + tz.transition 2009, 10, :o4, 1257012000 + tz.transition 2010, 4, :o5, 1271271600 + tz.transition 2010, 10, :o4, 1288548000 + tz.transition 2011, 4, :o5, 1302807600 + tz.transition 2011, 10, :o4, 1320084000 + tz.transition 2012, 4, :o5, 1334430000 + tz.transition 2012, 10, :o4, 1351706400 + tz.transition 2013, 4, :o5, 1365966000 + tz.transition 2013, 10, :o4, 1383242400 + tz.transition 2014, 4, :o5, 1397502000 + tz.transition 2014, 10, :o4, 1414778400 + tz.transition 2015, 4, :o5, 1429038000 + tz.transition 2015, 10, :o4, 1446314400 + tz.transition 2016, 4, :o5, 1460660400 + tz.transition 2016, 10, :o4, 1477936800 + tz.transition 2017, 4, :o5, 1492196400 + tz.transition 2017, 10, :o4, 1509472800 + tz.transition 2018, 4, :o5, 1523732400 + tz.transition 2018, 10, :o4, 1541008800 + tz.transition 2019, 4, :o5, 1555268400 + tz.transition 2019, 10, :o4, 1572544800 + tz.transition 2020, 4, :o5, 1586890800 + tz.transition 2020, 10, :o4, 1604167200 + tz.transition 2021, 4, :o5, 1618426800 + tz.transition 2021, 10, :o4, 1635703200 + tz.transition 2022, 4, :o5, 1649962800 + tz.transition 2022, 10, :o4, 1667239200 + tz.transition 2023, 4, :o5, 1681498800 + tz.transition 2023, 10, :o4, 1698775200 + tz.transition 2024, 4, :o5, 1713121200 + tz.transition 2024, 10, :o4, 1730397600 + tz.transition 2025, 4, :o5, 1744657200 + tz.transition 2025, 10, :o4, 1761933600 + tz.transition 2026, 4, :o5, 1776193200 + tz.transition 2026, 10, :o4, 1793469600 + tz.transition 2027, 4, :o5, 1807729200 + tz.transition 2027, 10, :o4, 1825005600 + tz.transition 2028, 4, :o5, 1839351600 + tz.transition 2028, 10, :o4, 1856628000 + tz.transition 2029, 4, :o5, 1870887600 + tz.transition 2029, 10, :o4, 1888164000 + tz.transition 2030, 4, :o5, 1902423600 + tz.transition 2030, 10, :o4, 1919700000 + tz.transition 2031, 4, :o5, 1933959600 + tz.transition 2031, 10, :o4, 1951236000 + tz.transition 2032, 4, :o5, 1965582000 + tz.transition 2032, 10, :o4, 1982858400 + tz.transition 2033, 4, :o5, 1997118000 + tz.transition 2033, 10, :o4, 2014394400 + tz.transition 2034, 4, :o5, 2028654000 + tz.transition 2034, 10, :o4, 2045930400 + tz.transition 2035, 4, :o5, 2060190000 + tz.transition 2035, 10, :o4, 2077466400 + tz.transition 2036, 4, :o5, 2091812400 + tz.transition 2036, 10, :o4, 2109088800 + tz.transition 2037, 4, :o5, 2123348400 + tz.transition 2037, 10, :o4, 2140624800 + tz.transition 2038, 4, :o5, 59172679, 24 + tz.transition 2038, 10, :o4, 9862913, 4 + tz.transition 2039, 4, :o5, 59181439, 24 + tz.transition 2039, 10, :o4, 9864373, 4 + tz.transition 2040, 4, :o5, 59190223, 24 + tz.transition 2040, 10, :o4, 9865837, 4 + tz.transition 2041, 4, :o5, 59198983, 24 + tz.transition 2041, 10, :o4, 9867297, 4 + tz.transition 2042, 4, :o5, 59207743, 24 + tz.transition 2042, 10, :o4, 9868757, 4 + tz.transition 2043, 4, :o5, 59216503, 24 + tz.transition 2043, 10, :o4, 9870217, 4 + tz.transition 2044, 4, :o5, 59225287, 24 + tz.transition 2044, 10, :o4, 9871681, 4 + tz.transition 2045, 4, :o5, 59234047, 24 + tz.transition 2045, 10, :o4, 9873141, 4 + tz.transition 2046, 4, :o5, 59242807, 24 + tz.transition 2046, 10, :o4, 9874601, 4 + tz.transition 2047, 4, :o5, 59251567, 24 + tz.transition 2047, 10, :o4, 9876061, 4 + tz.transition 2048, 4, :o5, 59260351, 24 + tz.transition 2048, 10, :o4, 9877525, 4 + tz.transition 2049, 4, :o5, 59269111, 24 + tz.transition 2049, 10, :o4, 9878985, 4 + tz.transition 2050, 4, :o5, 59277871, 24 + tz.transition 2050, 10, :o4, 9880445, 4 + end + end + end + end +end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kathmandu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kathmandu.rb index 37b241612e..37b241612e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kathmandu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kathmandu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kolkata.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kolkata.rb index 1b6ffbd59d..1b6ffbd59d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kolkata.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kolkata.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb index d6c503c155..d6c503c155 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb index 77a0c206fa..77a0c206fa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuwait.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuwait.rb index 5bd5283197..5bd5283197 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuwait.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuwait.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Magadan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Magadan.rb index 302093693e..302093693e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Magadan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Magadan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Muscat.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Muscat.rb index 604f651dfa..604f651dfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Muscat.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Muscat.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Novosibirsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Novosibirsk.rb index a4e7796e75..a4e7796e75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Novosibirsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Novosibirsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Rangoon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Rangoon.rb index 759b82d77a..759b82d77a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Rangoon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Rangoon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Riyadh.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Riyadh.rb index 7add410620..7add410620 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Riyadh.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Riyadh.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Seoul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Seoul.rb index 795d2a75df..795d2a75df 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Seoul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Seoul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Shanghai.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Shanghai.rb index 34b13d59ae..34b13d59ae 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Shanghai.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Shanghai.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Singapore.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Singapore.rb index b323a78f74..b323a78f74 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Singapore.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Singapore.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Taipei.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Taipei.rb index 3ba12108fb..3ba12108fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Taipei.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Taipei.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tashkent.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tashkent.rb index c205c7934d..c205c7934d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tashkent.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tashkent.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tbilisi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tbilisi.rb index 15792a5651..15792a5651 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tbilisi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tbilisi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tehran.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tehran.rb index d8df964a46..d8df964a46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tehran.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tehran.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tokyo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tokyo.rb index 51c9e16421..51c9e16421 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tokyo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tokyo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb index 2854f5c5fd..2854f5c5fd 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Urumqi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Urumqi.rb index d793ff1341..d793ff1341 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Urumqi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Urumqi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Vladivostok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Vladivostok.rb index bd9e3d60ec..bd9e3d60ec 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Vladivostok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Vladivostok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yakutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yakutsk.rb index 56435a788f..56435a788f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yakutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yakutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yekaterinburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yekaterinburg.rb index 8ef8df4a41..8ef8df4a41 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yekaterinburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yekaterinburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yerevan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yerevan.rb index e7f160861f..e7f160861f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yerevan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yerevan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Azores.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Azores.rb index 1bd16a75ac..1bd16a75ac 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Azores.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Azores.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb index 61c8c15043..61c8c15043 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/South_Georgia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/South_Georgia.rb index 6a4cbafb9f..6a4cbafb9f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/South_Georgia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/South_Georgia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Adelaide.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Adelaide.rb index c5d561cc1e..c5d561cc1e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Adelaide.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Adelaide.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Brisbane.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Brisbane.rb index dd85ddae94..dd85ddae94 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Brisbane.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Brisbane.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Darwin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Darwin.rb index 17de88124d..17de88124d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Darwin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Darwin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Hobart.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Hobart.rb index 11384b9840..11384b9840 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Hobart.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Hobart.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Melbourne.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Melbourne.rb index c1304488ea..c1304488ea 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Melbourne.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Melbourne.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Perth.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Perth.rb index d9e66f14a8..d9e66f14a8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Perth.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Perth.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Sydney.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Sydney.rb index 9062bd7c3c..9062bd7c3c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Sydney.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Sydney.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Etc/UTC.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Etc/UTC.rb index 28b2c6a04c..28b2c6a04c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Etc/UTC.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Etc/UTC.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Amsterdam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Amsterdam.rb index 2d0c95c4bc..2d0c95c4bc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Amsterdam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Amsterdam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Athens.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Athens.rb index 4e21e535ca..4e21e535ca 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Athens.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Athens.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Belgrade.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Belgrade.rb index 4dbd893d75..4dbd893d75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Belgrade.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Belgrade.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Berlin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Berlin.rb index 721054236c..721054236c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Berlin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Berlin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bratislava.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bratislava.rb index 7a731a0b6a..7a731a0b6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bratislava.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bratislava.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Brussels.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Brussels.rb index 6b0a242944..6b0a242944 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Brussels.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Brussels.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bucharest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bucharest.rb index 521c3c932e..521c3c932e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bucharest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bucharest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Budapest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Budapest.rb index 1f3a9738b7..1f3a9738b7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Budapest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Budapest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Copenhagen.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Copenhagen.rb index 47cbaf14a7..47cbaf14a7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Copenhagen.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Copenhagen.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Dublin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Dublin.rb index 0560bb5436..0560bb5436 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Dublin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Dublin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Helsinki.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Helsinki.rb index 13a806bcc7..13a806bcc7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Helsinki.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Helsinki.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Istanbul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Istanbul.rb index 8306c47536..8306c47536 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Istanbul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Istanbul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Kiev.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Kiev.rb index 513d3308be..513d3308be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Kiev.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Kiev.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Lisbon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Lisbon.rb index 1c6d2a3d30..1c6d2a3d30 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Lisbon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Lisbon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Ljubljana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Ljubljana.rb index a9828e6ef8..a9828e6ef8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Ljubljana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Ljubljana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/London.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/London.rb index 64ce41e900..64ce41e900 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/London.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/London.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Madrid.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Madrid.rb index 1fb568239a..1fb568239a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Madrid.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Madrid.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Minsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Minsk.rb index fa15816cc8..fa15816cc8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Minsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Minsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Moscow.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Moscow.rb index ef269b675b..ef269b675b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Moscow.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Moscow.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Paris.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Paris.rb index e3236c0ba1..e3236c0ba1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Paris.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Paris.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Prague.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Prague.rb index bcabee96c1..bcabee96c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Prague.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Prague.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Riga.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Riga.rb index 784837f758..784837f758 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Riga.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Riga.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Rome.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Rome.rb index aa7b43d9d2..aa7b43d9d2 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Rome.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Rome.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sarajevo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sarajevo.rb index 068c5fe6ad..068c5fe6ad 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sarajevo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sarajevo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Skopje.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Skopje.rb index 10b71f285e..10b71f285e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Skopje.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Skopje.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sofia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sofia.rb index 38a70eceb9..38a70eceb9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sofia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sofia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Stockholm.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Stockholm.rb index 43db70fa61..43db70fa61 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Stockholm.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Stockholm.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Tallinn.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Tallinn.rb index de5a8569f3..de5a8569f3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Tallinn.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Tallinn.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vienna.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vienna.rb index 990aabab66..990aabab66 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vienna.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vienna.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vilnius.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vilnius.rb index d89d095a75..d89d095a75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vilnius.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vilnius.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Warsaw.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Warsaw.rb index 7fa51c2691..7fa51c2691 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Warsaw.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Warsaw.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Zagreb.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Zagreb.rb index ecdd903d28..ecdd903d28 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Zagreb.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Zagreb.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Auckland.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Auckland.rb index a524fd6b6b..a524fd6b6b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Auckland.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Auckland.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Fiji.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Fiji.rb index 5fe9bbd9a6..5fe9bbd9a6 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Fiji.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Fiji.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Guam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Guam.rb index d4c1a0a682..d4c1a0a682 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Guam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Guam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Honolulu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Honolulu.rb index 204b226537..204b226537 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Honolulu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Honolulu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Majuro.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Majuro.rb index 32adad92c1..32adad92c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Majuro.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Majuro.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Midway.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Midway.rb index 97784fcc10..97784fcc10 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Midway.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Midway.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Noumea.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Noumea.rb index 70173db8ab..70173db8ab 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Noumea.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Noumea.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Pago_Pago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Pago_Pago.rb index c8fcd7d527..c8fcd7d527 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Pago_Pago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Pago_Pago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Port_Moresby.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Port_Moresby.rb index f06cf6d54f..f06cf6d54f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Port_Moresby.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Port_Moresby.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Tongatapu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Tongatapu.rb index 7578d92f38..7578d92f38 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Tongatapu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Tongatapu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/info_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/info_timezone.rb index 001303c594..001303c594 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/info_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/info_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone.rb index f8ec4fca87..f8ec4fca87 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone_info.rb index 8197ff3e81..8197ff3e81 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/offset_rationals.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/offset_rationals.rb index b1f10b2b63..b1f10b2b63 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/offset_rationals.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/offset_rationals.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/ruby_core_support.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/ruby_core_support.rb index b65eeaaae7..9a0441206b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/ruby_core_support.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/ruby_core_support.rb @@ -1,56 +1,56 @@ -#-- -# Copyright (c) 2008 Philip Ross -# -# 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. -#++ - -require 'date' -require 'rational' - -module TZInfo - - # Methods to support different versions of Ruby. - module RubyCoreSupport #:nodoc: - - # Use Rational.new! for performance reasons in Ruby 1.8. - # This has been removed from 1.9, but Rational performs better. - if Rational.respond_to? :new! - def self.rational_new!(numerator, denominator = 1) - Rational.new!(numerator, denominator) - end - else - def self.rational_new!(numerator, denominator = 1) - Rational(numerator, denominator) - end - end - - # Ruby 1.8.6 introduced new! and deprecated new0. - # Ruby 1.9.0 removed new0. - # We still need to support new0 for older versions of Ruby. - if DateTime.respond_to? :new! - def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY) - DateTime.new!(ajd, of, sg) - end - else - def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY) - DateTime.new0(ajd, of, sg) - end - end - end +#--
+# Copyright (c) 2008 Philip Ross
+#
+# 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.
+#++
+
+require 'date'
+require 'rational'
+
+module TZInfo
+
+ # Methods to support different versions of Ruby.
+ module RubyCoreSupport #:nodoc:
+
+ # Use Rational.new! for performance reasons in Ruby 1.8.
+ # This has been removed from 1.9, but Rational performs better.
+ if Rational.respond_to? :new!
+ def self.rational_new!(numerator, denominator = 1)
+ Rational.new!(numerator, denominator)
+ end
+ else
+ def self.rational_new!(numerator, denominator = 1)
+ Rational(numerator, denominator)
+ end
+ end
+
+ # Ruby 1.8.6 introduced new! and deprecated new0.
+ # Ruby 1.9.0 removed new0.
+ # We still need to support new0 for older versions of Ruby.
+ if DateTime.respond_to? :new!
+ def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
+ DateTime.new!(ajd, of, sg)
+ end
+ else
+ def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
+ DateTime.new0(ajd, of, sg)
+ end
+ end
+ end
end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/time_or_datetime.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/time_or_datetime.rb index 264517f3ee..264517f3ee 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/time_or_datetime.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/time_or_datetime.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone.rb index ef4ecd8ae1..ef4ecd8ae1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_definition.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_definition.rb index 39ca8bfa53..39ca8bfa53 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_definition.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_definition.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_info.rb index 68e38c35fb..68e38c35fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_offset_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_offset_info.rb index 6a0bbca46f..6a0bbca46f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_offset_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_offset_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_period.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_period.rb index 00888fcfdc..00888fcfdc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_period.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_period.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_transition_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_transition_info.rb index 6b0669cc4a..6b0669cc4a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_transition_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_transition_info.rb diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index af9656615c..f390c66838 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -1,4 +1,12 @@ ORIG_ARGV = ARGV.dup +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/activerecord/lib") +end + require 'test/unit' @@ -11,7 +19,6 @@ rescue LoadError end ENV['NO_RELOAD'] = '1' -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_support' require 'active_support/test_case' diff --git a/actionpack/test/template/benchmark_helper_test.rb b/activesupport/test/benchmarkable_test.rb index ac31fc6503..766956f50f 100644 --- a/actionpack/test/template/benchmark_helper_test.rb +++ b/activesupport/test/benchmarkable_test.rb @@ -1,17 +1,10 @@ require 'abstract_unit' -require 'action_view/helpers/benchmark_helper' -class BenchmarkHelperTest < ActionView::TestCase - tests ActionView::Helpers::BenchmarkHelper - - def setup - super - controller.logger = ActiveSupport::BufferedLogger.new(StringIO.new) - controller.logger.auto_flushing = false - end +class BenchmarkableTest < ActiveSupport::TestCase + include ActiveSupport::Benchmarkable def teardown - controller.logger.send(:clear_buffer) + logger.send(:clear_buffer) end def test_without_block @@ -45,22 +38,22 @@ class BenchmarkHelperTest < ActionView::TestCase end def test_within_level - controller.logger.level = ActiveSupport::BufferedLogger::DEBUG + logger.level = ActiveSupport::BufferedLogger::DEBUG benchmark('included_debug_run', :level => :debug) { } assert_last_logged 'included_debug_run' end def test_outside_level - controller.logger.level = ActiveSupport::BufferedLogger::ERROR + logger.level = ActiveSupport::BufferedLogger::ERROR benchmark('skipped_debug_run', :level => :debug) { } assert_no_match(/skipped_debug_run/, buffer.last) ensure - controller.logger.level = ActiveSupport::BufferedLogger::DEBUG + logger.level = ActiveSupport::BufferedLogger::DEBUG end def test_without_silencing benchmark('debug_run', :silence => false) do - controller.logger.info "not silenced!" + logger.info "not silenced!" end assert_equal 2, buffer.size @@ -68,18 +61,25 @@ class BenchmarkHelperTest < ActionView::TestCase def test_with_silencing benchmark('debug_run', :silence => true) do - controller.logger.info "silenced!" + logger.info "silenced!" end assert_equal 1, buffer.size end - private + def logger + @logger ||= begin + logger = ActiveSupport::BufferedLogger.new(StringIO.new) + logger.auto_flushing = false + logger + end + end + def buffer - controller.logger.send(:buffer) + logger.send(:buffer) end - + def assert_last_logged(message = 'Benchmarking') assert_match(/^#{message} \(.*\)$/, buffer.last) end diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index b51d68551d..beb55ba17e 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -52,6 +52,13 @@ class DelegatingAttributesTest < Test::Unit::TestCase assert !single_class.public_instance_methods.map(&:to_s).include?("both=") end + def test_simple_accessor_declaration_with_instance_reader_false + single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false + assert single_class.respond_to?(:no_instance_reader) + assert single_class.respond_to?(:no_instance_reader=) + assert !single_class.public_instance_methods.map(&:to_s).include?("no_instance_reader") + end + def test_working_with_simple_attributes single_class.superclass_delegating_accessor :both diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb index cc3f07d5c5..68b089d5b4 100644 --- a/activesupport/test/core_ext/regexp_ext_test.rb +++ b/activesupport/test/core_ext/regexp_ext_test.rb @@ -2,28 +2,9 @@ require 'abstract_unit' require 'active_support/core_ext/regexp' class RegexpExtAccessTests < Test::Unit::TestCase - def test_number_of_captures - assert_equal 0, //.number_of_captures - assert_equal 1, /.(.)./.number_of_captures - assert_equal 2, /.(.).(?:.).(.)/.number_of_captures - assert_equal 3, /.((.).(?:.).(.))/.number_of_captures - end - def test_multiline assert_equal true, //m.multiline? assert_equal false, //.multiline? assert_equal false, /(?m:)/.multiline? end - - def test_optionalize - assert_equal "a?", Regexp.optionalize("a") - assert_equal "(?:foo)?", Regexp.optionalize("foo") - assert_equal "", Regexp.optionalize("") - end - - def test_unoptionalize - assert_equal "a", Regexp.unoptionalize("a?") - assert_equal "foo", Regexp.unoptionalize("(?:foo)?") - assert_equal "", Regexp.unoptionalize("") - end end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb new file mode 100644 index 0000000000..9175c8f26e --- /dev/null +++ b/activesupport/test/notifications_test.rb @@ -0,0 +1,201 @@ +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 + +protected + + def random_id + @random_id ||= ActiveSupport::SecureRandom.hex(10) + end + + def event(*args) + ActiveSupport::Notifications::Event.new(*args) + 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) + 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 + + assert_equal 2, result + end + + def test_events_are_published_to_a_listener + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + 1 + 1 + end + + sleep(0.1) + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_nested_events_can_be_instrumented + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + ActiveSupport::Notifications.instrument(:wot, :payload => "child") do + 1 + 1 + end + + sleep(0.1) + + assert_equal 1, @events.size + assert_equal :wot, @events.first.name + assert_equal Hash[:payload => "child"], @events.first.payload + end + + sleep(0.1) + + assert_equal 2, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + assert_in_delta 100, @events.last.duration, 70 + end + + def test_event_is_pushed_even_if_block_fails + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + raise "OMG" + end rescue RuntimeError + + sleep(0.1) + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_event_is_pushed_even_without_block + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") + sleep(0.1) + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_subscribed_in_a_transaction + @another = [] + + ActiveSupport::Notifications.subscribe("cache") do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:cache){ 1 } + ActiveSupport::Notifications.transaction do + ActiveSupport::Notifications.instrument(:cache){ 1 } + end + ActiveSupport::Notifications.instrument(:cache){ 1 } + + sleep 0.1 + + before, during, after = @another.map {|e| e.transaction_id } + assert_equal before, after + assert_not_equal before, during + end + + def test_subscriber_with_pattern + @another = [] + + ActiveSupport::Notifications.subscribe("cache") do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:cache){ 1 } + + sleep(0.1) + + assert_equal 1, @another.size + assert_equal :cache, @another.first.name + assert_equal 1, @another.first.result + end + + def test_subscriber_with_pattern_as_regexp + @another = [] + ActiveSupport::Notifications.subscribe(/cache/) do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:something){ 0 } + ActiveSupport::Notifications.instrument(:cache){ 1 } + + sleep(0.1) + + 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) + end + + 1.upto(100) do |i| + ActiveSupport::Notifications.instrument(:value){ i } + end + + sleep 0.1 + + assert_equal 100, @events.size + assert_equal :value, @events.first.name + assert_equal 1, @events.first.result + assert_equal 100, @events.last.result + + assert_equal 100, @another.size + assert_equal :value, @another.first.name + assert_equal 1, @another.first.result + assert_equal 100, @another.last.result + end +end diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb deleted file mode 100644 index 683cc36f6a..0000000000 --- a/activesupport/test/orchestra_test.rb +++ /dev/null @@ -1,161 +0,0 @@ -require 'abstract_unit' - -class OrchestraEventTest < Test::Unit::TestCase - def setup - @parent = ActiveSupport::Orchestra::Event.new(:parent) - end - - def test_initialization_with_name_and_parent_and_payload - event = ActiveSupport::Orchestra::Event.new(:awesome, @parent, :payload => "orchestra") - assert_equal(:awesome, event.name) - assert_equal(@parent, event.parent) - assert_equal({ :payload => "orchestra" }, event.payload) - end - - def test_thread_id_is_set_on_initialization - event = ActiveSupport::Orchestra::Event.new(:awesome) - assert_equal Thread.current.object_id, event.thread_id - end - - def test_current_time_is_set_on_initialization - previous_time = Time.now.utc - event = ActiveSupport::Orchestra::Event.new(:awesome) - assert_kind_of Time, event.time - assert event.time.to_f >= previous_time.to_f - end - - def test_duration_is_set_when_event_finishes - event = ActiveSupport::Orchestra::Event.new(:awesome) - sleep(0.1) - event.finish! - assert_in_delta 100, event.duration, 30 - end -end - -class OrchestraMainTest < Test::Unit::TestCase - def setup - @listener = [] - ActiveSupport::Orchestra.register @listener - end - - def teardown - ActiveSupport::Orchestra.unregister @listener - end - - def test_orchestra_allows_any_action_to_be_instrumented - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - sleep(0.1) - end - - assert_equal :awesome, event.name - assert_equal "orchestra", event.payload - assert_in_delta 100, event.duration, 30 - end - - def test_block_result_is_stored - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - 1 + 1 - end - - assert_equal 2, event.result - end - - def test_events_are_published_to_a_listener - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - 1 + 1 - end - - assert_equal 1, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - end - - def test_nested_events_can_be_instrumented - ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - ActiveSupport::Orchestra.instrument(:wot, "child") do - sleep(0.1) - end - - assert_equal 1, @listener.size - assert_equal :wot, @listener.first.name - assert_equal "child", @listener.first.payload - - assert_nil @listener.first.parent.duration - assert_in_delta 100, @listener.first.duration, 30 - end - - assert_equal 2, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - assert_in_delta 100, @listener.first.parent.duration, 30 - end - - def test_event_is_pushed_even_if_block_fails - ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - raise "OMG" - end rescue RuntimeError - - assert_equal 1, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - end -end - -class OrchestraListenerTest < Test::Unit::TestCase - class MyListener < ActiveSupport::Orchestra::Listener - attr_reader :consumed - - def consume(event) - @consumed ||= [] - @consumed << event - end - end - - def setup - @listener = MyListener.new - ActiveSupport::Orchestra.register @listener - end - - def teardown - ActiveSupport::Orchestra.unregister @listener - end - - def test_thread_is_exposed_by_listener - assert_kind_of Thread, @listener.thread - end - - def test_event_is_consumed_when_an_action_is_instrumented - ActiveSupport::Orchestra.instrument(:sum) do - 1 + 1 - end - sleep 0.1 - assert_equal 1, @listener.consumed.size - assert_equal :sum, @listener.consumed.first.name - assert_equal 2, @listener.consumed.first.result - end - - def test_with_sevaral_consumers_and_several_events - @another = MyListener.new - ActiveSupport::Orchestra.register @another - - 1.upto(100) do |i| - ActiveSupport::Orchestra.instrument(:value) do - i - end - end - - sleep 0.1 - - assert_equal 100, @listener.consumed.size - assert_equal :value, @listener.consumed.first.name - assert_equal 1, @listener.consumed.first.result - assert_equal 100, @listener.consumed.last.result - - assert_equal 100, @another.consumed.size - assert_equal :value, @another.consumed.first.name - assert_equal 1, @another.consumed.first.result - assert_equal 100, @another.consumed.last.result - ensure - ActiveSupport::Orchestra.unregister @another - end -end diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 3b06c9c7ce..b2f9f59bec 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -16,6 +16,15 @@ root_dir = File.expand_path(File.dirname(__FILE__) + "/..") # A security hole, but there is nothing valuable on rails CI box anyway. build_results[:geminstaller] = system "sudo geminstaller --config=#{root_dir}/ci/geminstaller.yml --exceptions" +rm_f "#{root_dir}/vendor" +system "rm -rf #{root_dir}/*/vendor" +cd root_dir do + puts + puts "[CruiseControl] Bundling RubyGems" + puts + build_results[:bundle] = system 'gem bundle' +end + cd "#{root_dir}/activesupport" do puts puts "[CruiseControl] Building ActiveSupport" @@ -29,21 +38,21 @@ cd "#{root_dir}/activerecord" do puts puts "[CruiseControl] Building ActiveRecord with MySQL" puts - build_results[:activerecord_mysql] = system 'gem bundle && rake mysql:rebuild_databases && rake test_mysql' + build_results[:activerecord_mysql] = system 'rake mysql:rebuild_databases && rake test_mysql' end cd "#{root_dir}/activerecord" do puts puts "[CruiseControl] Building ActiveRecord with PostgreSQL" puts - build_results[:activerecord_postgresql8] = system 'gem bundle && rake postgresql:rebuild_databases && rake test_postgresql' + build_results[:activerecord_postgresql8] = system 'rake postgresql:rebuild_databases && rake test_postgresql' end cd "#{root_dir}/activerecord" do puts puts "[CruiseControl] Building ActiveRecord with SQLite 3" puts - build_results[:activerecord_sqlite3] = system 'gem bundle && rake test_sqlite3' + build_results[:activerecord_sqlite3] = system 'rake test_sqlite3' end cd "#{root_dir}/activemodel" do @@ -65,7 +74,7 @@ cd "#{root_dir}/actionpack" do puts puts "[CruiseControl] Building ActionPack" puts - build_results[:actionpack] = system 'gem bundle && rake' + build_results[:actionpack] = system 'rake' build_results[:actionpack_isolated] = system 'rake test:isolated' end diff --git a/railties/.gitignore b/railties/.gitignore new file mode 100644 index 0000000000..80dd262d2f --- /dev/null +++ b/railties/.gitignore @@ -0,0 +1 @@ +log/ diff --git a/railties/Rakefile b/railties/Rakefile index 0ba5ca10c2..3a87a88ddc 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -6,7 +6,12 @@ require 'rake/gempackagetask' require 'date' require 'rbconfig' -require File.join(File.dirname(__FILE__), 'lib/rails', 'version') +begin + require File.expand_path('../../vendor/gems/environment', __FILE__) +rescue LoadError +end +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" +require 'rails' PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' PKG_NAME = ENV['PKG_NAME'] || 'rails' @@ -81,7 +86,6 @@ end # Run application generator ------------------------------------------------------------- task :create_rails do - $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" require 'rails/generators' require 'rails/generators/rails/app/app_generator' Rails::Generators::AppGenerator.start [ File.basename(PKG_DESTINATION), "--quiet" ], diff --git a/railties/bin/rails b/railties/bin/rails index e743aa83f1..808df97429 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -19,6 +19,7 @@ end ARGV << "--help" if ARGV.empty? +require 'rails' require 'rails/generators' require 'rails/generators/rails/app/app_generator' diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb index 48d89ad06a..c3784cff32 100644 --- a/railties/builtin/rails_info/rails/info.rb +++ b/railties/builtin/rails_info/rails/info.rb @@ -75,7 +75,7 @@ module Rails protected def rails_vendor_root - @rails_vendor_root ||= "#{RAILS_ROOT}/vendor/rails" + @rails_vendor_root ||= "#{Rails.root}/vendor/rails" end def git_info @@ -124,7 +124,7 @@ module Rails # The application's location on the filesystem. property 'Application root' do - File.expand_path(RAILS_ROOT) + File.expand_path(Rails.root) end # The current Rails environment (development, test, or production). diff --git a/railties/guides/source/2_2_release_notes.textile b/railties/guides/source/2_2_release_notes.textile index f60af01050..15a7bdbd44 100644 --- a/railties/guides/source/2_2_release_notes.textile +++ b/railties/guides/source/2_2_release_notes.textile @@ -51,7 +51,7 @@ If you want to generate these guides locally, inside your application: rake doc:guides </ruby> -This will put the guides inside +RAILS_ROOT/doc/guides+ and you may start surfing straight away by opening +RAILS_ROOT/doc/guides/index.html+ in your favourite browser. +This will put the guides inside +Rails.root/doc/guides+ and you may start surfing straight away by opening +Rails.root/doc/guides/index.html+ in your favourite browser. * Lead Contributors: "Rails Documentation Team":http://guides.rails.info/credits.html * Major contributions from "Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai":http://izumi.plan99.net/blog/. diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 756caea5fe..46a28da8c4 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -653,7 +653,7 @@ class ClientsController < ApplicationController # Stream a file that has already been generated and stored on disk. def download_pdf client = Client.find(params[:id]) - send_data("#{RAILS_ROOT}/files/clients/#{client.id}.pdf", + send_data("#{Rails.root}/files/clients/#{client.id}.pdf", :filename => "#{client.name}.pdf", :type => "application/pdf") end diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index d042458419..1a571358a1 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -424,10 +424,10 @@ INFO: For a good rundown on generators, see "Understanding Generators":http://wi Generators are code that generates code. Let's experiment by building one. Our generator will generate a text file. -The Rails generator by default looks in these places for available generators, where RAILS_ROOT is the root of your Rails application, like /home/foobar/commandsapp: +The Rails generator by default looks in these places for available generators, where Rails.root is the root of your Rails application, like /home/foobar/commandsapp: -* RAILS_ROOT/lib/generators -* RAILS_ROOT/vendor/generators +* Rails.root/lib/generators +* Rails.root/vendor/generators * Inside any plugin with a directory like "generators" or "rails_generators" * ~/.rails/generators * Inside any Gem you have installed with a name ending in "_generator" @@ -465,7 +465,7 @@ We take whatever args are supplied, save them to an instance variable, and liter * Check there's a *public* directory. You bet there is. * Run the ERb template called "tutorial.erb". -* Save it into "RAILS_ROOT/public/tutorial.txt". +* Save it into "Rails.root/public/tutorial.txt". * Pass in the arguments we saved through the +:assign+ parameter. Next we'll build the template: diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a0e5d6a5a5..26a55a73e1 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -3,6 +3,11 @@ module Rails extend Initializable class << self + # Stub out App initialize + def initialize! + new + end + def config @config ||= Configuration.new end @@ -36,13 +41,13 @@ module Rails end def new - initializers.run + run_initializers self end end initializer :initialize_rails do - Rails.initializers.run + Rails.run_initializers end # Set the <tt>$LOAD_PATH</tt> based on the value of @@ -52,13 +57,6 @@ module Rails $LOAD_PATH.uniq! end - # Bail if boot.rb is outdated - initializer :freak_out_if_boot_rb_is_outdated do - unless defined?(Rails::BOOTSTRAP_VERSION) - abort %{Your config/boot.rb is outdated: Run "rake rails:update".} - end - 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. @@ -128,15 +126,6 @@ module Rails end end - initializer :add_gem_load_paths do - require 'rails/gem_dependency' - Rails::GemDependency.add_frozen_gem_path - unless config.gems.empty? - require "rubygems" - config.gems.each { |gem| gem.add_load_paths } - end - end - # Preload all frameworks specified by the Configuration#frameworks. # Used by Passenger to ensure everything's loaded before forking and # to avoid autoload race conditions in JRuby. @@ -322,31 +311,6 @@ module Rails end end - initializer :check_for_unbuilt_gems do - unbuilt_gems = config.gems.select {|gem| gem.frozen? && !gem.built? } - if unbuilt_gems.size > 0 - # don't print if the gems:build rake tasks are being run - unless $gems_build_rake_task - abort <<-end_error - The following gems have native components that need to be built - #{unbuilt_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "} - - You're running: - ruby #{Gem.ruby_version} at #{Gem.ruby} - rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '} - - Run `rake gems:build` to build the unbuilt gems. - end_error - end - end - end - - initializer :load_gems do - unless $gems_rake_task - config.gems.each { |gem| gem.load } - end - end - # Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt> # defaults to <tt>vendor/plugins</tt> but may also be set to a list of # paths, such as @@ -367,49 +331,19 @@ module Rails plugin_loader.load_plugins end - # TODO: Figure out if this needs to run a second time - # load_gems - - initializer :check_gem_dependencies do - unloaded_gems = config.gems.reject { |g| g.loaded? } - if unloaded_gems.size > 0 - configuration.gems_dependencies_loaded = false - # don't print if the gems rake tasks are being run - unless $gems_rake_task - abort <<-end_error - Missing these required gems: - #{unloaded_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "} - - You're running: - ruby #{Gem.ruby_version} at #{Gem.ruby} - rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '} - - Run `rake gems:install` to install the missing gems. - end_error - end - else - configuration.gems_dependencies_loaded = true - 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 - if config.gems_dependencies_loaded - Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer| - load(initializer) - end + Dir["#{configuration.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 - if config.gems_dependencies_loaded - configuration.after_initialize_blocks.each do |block| - block.call - end + configuration.after_initialize_blocks.each do |block| + block.call end end @@ -451,7 +385,7 @@ module Rails # # # Observers are loaded after plugins in case Observers or observed models are modified by plugins. initializer :load_observers do - if config.gems_dependencies_loaded && configuration.frameworks.include?(:active_record) + if configuration.frameworks.include?(:active_record) ActiveRecord::Base.instantiate_observers end end @@ -487,5 +421,16 @@ module Rails Rails::Generators.options.deep_merge! config.generators.options end end + + # For each framework, search for instrument file with Notifications hooks. + # + initializer :load_notifications_hooks do + config.frameworks.each do |framework| + begin + require "#{framework}/notifications" + rescue LoadError => e + end + end + end end end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 9ff8367807..cd7dd0f80a 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -1,5 +1,4 @@ require 'active_support/backtrace_cleaner' -require 'rails/gem_dependency' module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner @@ -18,7 +17,7 @@ module Rails def initialize super - add_filter { |line| line.sub("#{RAILS_ROOT}/", '') } + add_filter { |line| line.sub("#{Rails.root}/", '') } add_filter { |line| line.sub(ERB_METHOD_SIG, '') } add_filter { |line| line.sub('./', '/') } # for tests @@ -28,17 +27,14 @@ module Rails add_silencer { |line| RAILS_GEMS.any? { |gem| line =~ /^#{gem} / } } add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) } end - - + private def add_gem_filters + return unless defined? Gem (Gem.path + [Gem.default_dir]).uniq.each do |path| # http://gist.github.com/30430 add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} end - - vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{RAILS_ROOT}/",'') - add_filter { |line| line.sub(/(#{vendor_gems_path})\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) [v] \4')} end end diff --git a/railties/lib/rails/commands/about.rb b/railties/lib/rails/commands/about.rb index 9a05c47390..d4c30bbeb2 100644 --- a/railties/lib/rails/commands/about.rb +++ b/railties/lib/rails/commands/about.rb @@ -1,4 +1,2 @@ -require "#{RAILS_ROOT}/config/application" -Rails.application.new require 'rails/info' puts Rails::Info diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 31448bdf1a..b977b7162f 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -12,7 +12,7 @@ OptionParser.new do |opt| end libs = " -r irb/completion" -libs << %( -r "#{RAILS_ROOT}/config/environment") +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" diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index e6f11a45db..4e699acf6b 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -25,7 +25,7 @@ OptionParser.new do |opt| end env = ARGV.first || ENV['RAILS_ENV'] || 'development' -unless config = YAML::load(ERB.new(IO.read(RAILS_ROOT + "/config/database.yml")).result)[env] +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 diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb index 860c03e01e..1270fdd033 100644 --- a/railties/lib/rails/commands/destroy.rb +++ b/railties/lib/rails/commands/destroy.rb @@ -1,5 +1,4 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) -require "#{RAILS_ROOT}/config/application" if ARGV.size == 0 Rails::Generators.help diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index cfa6a51d94..d91dcf9788 100755 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -1,5 +1,4 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) -Rails.application.new if ARGV.size == 0 Rails::Generators.help diff --git a/railties/lib/rails/commands/performance/benchmarker.rb b/railties/lib/rails/commands/performance/benchmarker.rb index 5039c5408c..dfba4bf034 100644 --- a/railties/lib/rails/commands/performance/benchmarker.rb +++ b/railties/lib/rails/commands/performance/benchmarker.rb @@ -12,7 +12,6 @@ end require 'benchmark' include Benchmark -Rails.application.new # Don't include compilation in the benchmark ARGV.each { |expression| eval(expression) } @@ -21,4 +20,4 @@ bm(6) do |x| ARGV.each_with_index do |expression, idx| x.report("##{idx + 1}") { N.times { eval(expression) } } end -end +end diff --git a/railties/lib/rails/commands/performance/profiler.rb b/railties/lib/rails/commands/performance/profiler.rb index 7274e2dfb7..aaa075018c 100644 --- a/railties/lib/rails/commands/performance/profiler.rb +++ b/railties/lib/rails/commands/performance/profiler.rb @@ -3,10 +3,6 @@ if ARGV.empty? exit(1) end -# Keep the expensive require out of the profile. -$stderr.puts 'Loading Rails...' -Rails.application.new # Initialize the application - # Define a method to profile. if ARGV[1] and ARGV[1].to_i > 1 eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index d24f36dd63..0246348c77 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -36,8 +36,6 @@ ARGV.delete(code_or_file) ENV["RAILS_ENV"] = options[:environment] RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV) -Rails.application.new - begin if code_or_file.nil? $stderr.puts "Run '#{$0} -h' for help." diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index c138cbc9bf..29359e49a4 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -7,7 +7,7 @@ options = { :Port => 3000, :Host => "0.0.0.0", :environment => (ENV['RAILS_ENV'] || "development").dup, - :config => RAILS_ROOT + "/config.ru", + :config => "#{Rails.root}/config.ru", :detach => false, :debugger => false } @@ -46,7 +46,7 @@ puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]} if options[:detach] Process.daemon - pid = "#{RAILS_ROOT}/tmp/pids/server.pid" + 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 diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 322590f108..0a545f23de 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,10 +1,11 @@ require 'rails/plugin/loader' require 'rails/plugin/locator' +require 'active_support/ordered_options' module Rails class Configuration attr_accessor :cache_classes, :load_paths, - :load_once_paths, :gems_dependencies_loaded, :after_initialize_blocks, + :load_once_paths, :after_initialize_blocks, :frameworks, :framework_root_path, :root, :plugin_paths, :plugins, :plugin_loader, :plugin_locators, :gems, :loaded_plugins, :reload_plugins, :i18n, :gems, :whiny_nils, :consider_all_requests_local, @@ -23,9 +24,9 @@ module Rails @serve_static_assets = true for framework in frameworks - self.send("#{framework}=", Rails::OrderedOptions.new) + self.send("#{framework}=", ActiveSupport::OrderedOptions.new) end - self.active_support = Rails::OrderedOptions.new + self.active_support = ActiveSupport::OrderedOptions.new end def after_initialize(&blk) @@ -34,37 +35,25 @@ module Rails def root @root ||= begin - if defined?(RAILS_ROOT) - root = RAILS_ROOT - else - call_stack = caller.map { |p| p.split(':').first } - root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] } - root_path = File.dirname(root_path) - - while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru") - parent = File.dirname(root_path) - root_path = parent != root_path && parent - end + call_stack = caller.map { |p| p.split(':').first } + root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] } + root_path = File.dirname(root_path) - Object.class_eval("RAILS_ROOT = ''") - - root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru") + parent = File.dirname(root_path) + root_path = parent != root_path && parent end - root = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? - Pathname.new(root).expand_path.to_s : - Pathname.new(root).realpath.to_s + root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd - # TODO: Remove RAILS_ROOT - RAILS_ROOT.replace(root) - root + RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? + Pathname.new(root).expand_path : + Pathname.new(root).realpath end end def root=(root) - Object.class_eval("RAILS_ROOT = ''") unless defined?(RAILS_ROOT) - RAILS_ROOT.replace(root) - @root = root + @root = Pathname.new(root).expand_path end def paths @@ -230,7 +219,7 @@ module Rails def i18n @i18n ||= begin - i18n = Rails::OrderedOptions.new + i18n = ActiveSupport::OrderedOptions.new i18n.load_path = [] if File.exist?(File.join(root, 'config', 'locales')) @@ -242,25 +231,6 @@ module Rails end end - # Adds a single Gem dependency to the rails application. By default, it will require - # the library with the same name as the gem. Use :lib to specify a different name. - # - # # gem 'aws-s3', '>= 0.4.0' - # # require 'aws/s3' - # config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', \ - # :source => "http://code.whytheluckystiff.net" - # - # To require a library be installed, but not attempt to load it, pass :lib => false - # - # config.gem 'qrp', :version => '0.4.1', :lib => false - def gem(name, options = {}) - gems << Rails::GemDependency.new(name, options) - end - - def gems - @gems ||= [] - end - def environment_path "#{root}/config/environments/#{RAILS_ENV}.rb" end @@ -290,6 +260,14 @@ module Rails end end + # Allows Notifications queue to be modified. + # + # config.notifications.queue = MyNewQueue.new + # + def notifications + ActiveSupport::Notifications + end + class Generators #:nodoc: attr_accessor :aliases, :options, :colorize_logging diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb index 929c38bd22..a5e51ad04a 100644 --- a/railties/lib/rails/core.rb +++ b/railties/lib/rails/core.rb @@ -6,7 +6,7 @@ module Rails # TODO: w0t? class << self def application - @@application + @@application ||= nil end def application=(application) @@ -18,6 +18,10 @@ module Rails application.configuration end + def initialize! + application.initialize! + end + def initialized? @initialized || false end @@ -43,7 +47,7 @@ module Rails end def root - Pathname.new(RAILS_ROOT) if defined?(RAILS_ROOT) + application && application.config.root end def env @@ -66,36 +70,4 @@ module Rails @@public_path = path end end - - class OrderedOptions < Array #:nodoc: - def []=(key, value) - key = key.to_sym - - if pair = find_pair(key) - pair.pop - pair << value - else - self << [key, value] - end - end - - def [](key) - pair = find_pair(key.to_sym) - pair ? pair.last : nil - end - - def method_missing(name, *args) - if name.to_s =~ /(.*)=$/ - self[$1.to_sym] = args.first - else - self[name] - end - end - - private - def find_pair(key) - self.each { |i| return i if i.first == key } - return false - end - end end
\ No newline at end of file diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb new file mode 100644 index 0000000000..3c5b8bdec7 --- /dev/null +++ b/railties/lib/rails/deprecation.rb @@ -0,0 +1,25 @@ +require "active_support/string_inquirer" +require "active_support/deprecation" + +RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do + def target + Rails.root + end + + def replace(val) + puts OMG + end + + def warn(callstack, called, args) + msg = "RAILS_ROOT is deprecated! Use Rails.root instead." + ActiveSupport::Deprecation.warn(msg, callstack) + end +end).new + +module Rails + class Configuration + def gem(*args) + ActiveSupport::Deprecation.warn("config.gem has been deprecated in favor of the Gemfile.") + end + end +end
\ No newline at end of file diff --git a/railties/lib/rails/gem_builder.rb b/railties/lib/rails/gem_builder.rb deleted file mode 100644 index 79c61cc034..0000000000 --- a/railties/lib/rails/gem_builder.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rubygems' -require 'rubygems/installer' - -module Rails - - # this class hijacks the functionality of Gem::Installer by overloading its - # initializer to only provide the information needed by - # Gem::Installer#build_extensions (which happens to be what we have) - class GemBuilder < Gem::Installer - - def initialize(spec, gem_dir) - @spec = spec - @gem_dir = gem_dir - end - - # silence the underlying builder - def say(message) - end - - end -end diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb deleted file mode 100644 index 06d830ba24..0000000000 --- a/railties/lib/rails/gem_dependency.rb +++ /dev/null @@ -1,311 +0,0 @@ -require 'rails/vendor_gem_source_index' - -module Gem - def self.source_index=(index) - @@source_index = index - end -end - -module Rails - class GemDependency < Gem::Dependency - attr_accessor :lib, :source, :dep - - def self.unpacked_path - @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems') - end - - @@framework_gems = {} - - def self.add_frozen_gem_path - @@paths_loaded ||= begin - source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) - Gem.clear_paths - Gem.source_index = source_index - # loaded before us - we can't change them, so mark them - Gem.loaded_specs.each do |name, spec| - @@framework_gems[name] = spec - end - true - end - end - - def self.from_directory_name(directory_name, load_spec=true) - directory_name_parts = File.basename(directory_name).split('-') - name = directory_name_parts[0..-2].join('-') - version = directory_name_parts.last - result = self.new(name, :version => version) - spec_filename = File.join(directory_name, '.specification') - if load_spec - raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename) - spec = YAML::load_file(spec_filename) - result.specification = spec - end - result - rescue ArgumentError => e - raise "Unable to determine gem name and version from '#{directory_name}'" - end - - def initialize(name, options = {}) - require 'rubygems' unless Object.const_defined?(:Gem) - - if options[:requirement] - req = options[:requirement] - elsif options[:version] - req = Gem::Requirement.create(options[:version]) - else - req = Gem::Requirement.default - end - - @lib = options[:lib] - @source = options[:source] - @loaded = @frozen = @load_paths_added = false - - super(name, req) - end - - def add_load_paths - self.class.add_frozen_gem_path - return if @loaded || @load_paths_added - if framework_gem? - @load_paths_added = @loaded = @frozen = true - return - end - gem self - @spec = Gem.loaded_specs[name] - @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec - @load_paths_added = true - rescue Gem::LoadError - end - - def dependencies - return [] if framework_gem? - return [] unless installed? - specification.dependencies.reject do |dependency| - dependency.type == :development - end.map do |dependency| - GemDependency.new(dependency.name, :requirement => dependency.version_requirements) - end - end - - def specification - # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. - # error out if loaded version and requested version are incompatible. - @spec ||= begin - matches = Gem.source_index.search(self) - matches << @@framework_gems[name] if framework_gem? - if Gem.loaded_specs[name] then - # This gem is already loaded. If the currently loaded gem is not in the - # list of candidate gems, then we have a version conflict. - existing_spec = Gem.loaded_specs[name] - unless matches.any? { |spec| spec.version == existing_spec.version } then - raise Gem::Exception, - "can't activate #{@dep}, already activated #{existing_spec.full_name}" - end - # we're stuck with it, so change to match - version_requirements = Gem::Requirement.create("=#{existing_spec.version}") - existing_spec - else - # new load - matches.last - end - end - end - - def specification=(s) - @spec = s - end - - def requirement - r = version_requirements - (r == Gem::Requirement.default) ? nil : r - end - - def built? - return false unless frozen? - - if vendor_gem? - specification.extensions.each do |ext| - makefile = File.join(unpacked_gem_directory, File.dirname(ext), 'Makefile') - return false unless File.exists?(makefile) - end - end - - true - end - - def framework_gem? - @@framework_gems.has_key?(name) - end - - def frozen? - @frozen ||= vendor_rails? || vendor_gem? - end - - def installed? - Gem.loaded_specs.keys.include?(name) - end - - def load_paths_added? - # always try to add load paths - even if a gem is loaded, it may not - # be a compatible version (ie random_gem 0.4 is loaded and a later spec - # needs >= 0.5 - gem 'random_gem' will catch this and error out) - @load_paths_added - end - - def loaded? - @loaded ||= begin - if vendor_rails? - true - elsif specification.nil? - false - else - # check if the gem is loaded by inspecting $" - # specification.files lists all the files contained in the gem - gem_files = specification.files - # select only the files contained in require_paths - typically in bin and lib - require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/") - gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) } - # chop the leading directory off - a typical file might be in - # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb - gem_lib_files.map! { |f| f.split('/', 2)[1] } - # if any of the files from the above list appear in $", the gem is assumed to - # have been loaded - !(gem_lib_files & $").empty? - end - end - end - - def vendor_rails? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? - end - - def vendor_gem? - specification && File.exists?(unpacked_gem_directory) - end - - def build(options={}) - require 'rails/gem_builder' - return if specification.nil? - if options[:force] || !built? - return unless File.exists?(unpacked_specification_filename) - spec = YAML::load_file(unpacked_specification_filename) - Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions - puts "Built gem: '#{unpacked_gem_directory}'" - end - dependencies.each { |dep| dep.build(options) } - end - - def install - unless installed? - cmd = "#{gem_command} #{install_command.join(' ')}" - puts cmd - puts %x(#{cmd}) - end - end - - def load - return if @loaded || @load_paths_added == false - require(@lib || name) unless @lib == false - @loaded = true - rescue LoadError - puts $!.to_s - $!.backtrace.each { |b| puts b } - end - - def refresh - Rails::VendorGemSourceIndex.silence_spec_warnings = true - real_gems = Gem.source_index.installed_source_index - exact_dep = Gem::Dependency.new(name, "= #{specification.version}") - matches = real_gems.search(exact_dep) - installed_spec = matches.first - if frozen? - if installed_spec - # we have a real copy - # get a fresh spec - matches should only have one element - # note that there is no reliable method to check that the loaded - # spec is the same as the copy from real_gems - Gem.activate changes - # some of the fields - real_spec = Gem::Specification.load(matches.first.loaded_from) - write_specification(real_spec) - puts "Reloaded specification for #{name} from installed gems." - else - # the gem isn't installed locally - write out our current specs - write_specification(specification) - puts "Gem #{name} not loaded locally - writing out current spec." - end - else - if framework_gem? - puts "Gem directory for #{name} not found - check if it's loading before rails." - else - puts "Something bad is going on - gem directory not found for #{name}." - end - end - end - - def unpack(options={}) - unless frozen? || framework_gem? - FileUtils.mkdir_p unpack_base - Dir.chdir unpack_base do - Gem::GemRunner.new.run(unpack_command) - end - # Gem.activate changes the spec - get the original - real_spec = Gem::Specification.load(specification.loaded_from) - write_specification(real_spec) - end - dependencies.each { |dep| dep.unpack(options) } if options[:recursive] - end - - def write_specification(spec) - # copy the gem's specification into GEMDIR/.specification so that - # we can access information about the gem on deployment systems - # without having the gem installed - File.open(unpacked_specification_filename, 'w') do |file| - file.puts spec.to_yaml - end - end - - def ==(other) - self.name == other.name && self.requirement == other.requirement - end - alias_method :"eql?", :"==" - - private - - def gem_command - case RUBY_PLATFORM - when /win32/ - 'gem.bat' - when /java/ - 'jruby -S gem' - else - 'gem' - end - end - - def install_command - cmd = %w(install) << name - cmd << "--version" << %("#{requirement.to_s}") if requirement - cmd << "--source" << @source if @source - cmd - end - - def unpack_command - cmd = %w(unpack) << name - cmd << "--version" << "= "+specification.version.to_s if requirement - cmd - end - - def unpack_base - Rails::GemDependency.unpacked_path - end - - def unpacked_gem_directory - File.join(unpack_base, specification.full_name) - end - - def unpacked_specification_filename - File.join(unpacked_gem_directory, '.specification') - end - - end -end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 471eb45ee6..49f32aa0db 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -9,7 +9,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.11.6/lib") +$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.11.8/lib") require 'rails/generators/base' require 'rails/generators/named_base' @@ -92,8 +92,6 @@ module Rails generator_path = File.join(spec.full_gem_path, "lib/generators") paths << generator_path if File.exist?(generator_path) end - elsif defined?(RAILS_ROOT) - paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] end paths @@ -102,8 +100,8 @@ module Rails # Load paths from plugin. # def self.plugins_generators_paths - return [] unless defined?(RAILS_ROOT) - Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] + return [] unless Rails.root + Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "generators")] end # Hold configured generators fallbacks. If a plugin developer wants a @@ -143,7 +141,7 @@ module Rails def self.load_paths @load_paths ||= begin paths = [] - paths << File.join(RAILS_ROOT, "lib", "generators") if defined?(RAILS_ROOT) + paths << File.join(Rails.root, "lib", "generators") if Rails.root paths << File.join(Thor::Util.user_home, ".rails", "generators") paths += self.plugins_generators_paths paths += self.gems_generators_paths diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 1a849a0e02..fe6321af30 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -32,7 +32,7 @@ module Rails # GET index def self.all(klass) - raise NotImplementedError + "#{klass}.all" end # GET show @@ -40,34 +40,38 @@ module Rails # PUT update # DELETE destroy def self.find(klass, params=nil) - raise NotImplementedError + "#{klass}.find(#{params})" end # GET new # POST create def self.build(klass, params=nil) - raise NotImplementedError + if params + "#{klass}.new(#{params})" + else + "#{klass}.new" + end end # POST create def save - raise NotImplementedError + "#{name}.save" end # PUT update def update_attributes(params=nil) - raise NotImplementedError + "#{name}.update_attributes(#{params})" end # POST create # PUT update def errors - raise NotImplementedError + "#{name}.errors" end # DELETE destroy def destroy - raise NotImplementedError + "#{name}.destroy" end end end diff --git a/railties/lib/rails/generators/active_record.rb b/railties/lib/rails/generators/active_record.rb index c03ea59c1b..babad33db3 100644 --- a/railties/lib/rails/generators/active_record.rb +++ b/railties/lib/rails/generators/active_record.rb @@ -18,39 +18,5 @@ module ActiveRecord end end end - - class ActiveModel < Rails::Generators::ActiveModel #:nodoc: - def self.all(klass) - "#{klass}.all" - end - - def self.find(klass, params=nil) - "#{klass}.find(#{params})" - end - - def self.build(klass, params=nil) - if params - "#{klass}.new(#{params})" - else - "#{klass}.new" - end - end - - def save - "#{name}.save" - end - - def update_attributes(params=nil) - "#{name}.update_attributes(#{params})" - end - - def errors - "#{name}.errors" - end - - def destroy - "#{name}.destroy" - end - end end end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 720caa5b3f..7af99797ea 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -203,8 +203,8 @@ module Rails super base.source_root # Cache source root - if defined?(RAILS_ROOT) && base.name !~ /Base$/ - path = File.expand_path(File.join(RAILS_ROOT, 'lib', 'templates')) + if Rails.root && base.name !~ /Base$/ + path = File.expand_path(File.join(Rails.root, 'lib', 'templates')) if base.name.include?('::') base.source_paths << File.join(path, base.base_name, base.generator_name) else diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 49b13cae2b..93d9ac553d 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,4 +1,4 @@ -require 'digest/md5' +require 'digest/md5' require 'active_support/secure_random' require 'rails/version' unless defined?(Rails::VERSION) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index bcbaa432b7..3966c0f70d 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -1,10 +1,21 @@ # Gemfile is where you list all of your application's dependencies # -gem "rails", "<%= Rails::VERSION::STRING %>" +<%= "# " if options.freeze? %>gem "rails", "<%= Rails::VERSION::STRING %>" # # Bundling edge rails: -# gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git" +<%= "# " unless options.freeze? %>gem "rails", "<%= Rails::VERSION::STRING %>", :git => "git://github.com/rails/rails.git" -# You can list more dependencies here -# gem "nokogiri" -# gem "merb" # ;)
\ No newline at end of file +# Specify gemcutter as a gem source +# source "http://gemcutter.org" + +# Specify gems that this application depends on and have them installed with rake gems:install +# gem "bj" +# gem "hpricot", "0.6" +# gem "sqlite3-ruby", :require_as => "sqlite3" +# gem "aws-s3", :require_as => "aws/s3" + +# Specify gems that should only be required in certain environments +# gem "rspec", :only => :test +# only :test do +# gem "webrat" +# end diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile index 1dce9863f2..6b6d07e8cc 100755 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -1,7 +1,7 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path(File.join(File.dirname(__FILE__), 'config', 'environment')) +require File.expand_path('../config/application', __FILE__) require 'rake' require 'rake/testtask' diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru index 3702ad0466..509a0da5b7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -1,5 +1,5 @@ # Require your environment file to bootstrap Rails -require File.expand_path('../config/application', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) # Dispatch the request -run Rails.application.new +run Rails.application 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 bff74a7786..8008c6ba07 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -1,5 +1,4 @@ -# Bootstrap the Rails environment, frameworks, and default configuration -require File.expand_path(File.join(File.dirname(__FILE__), 'boot')) +require File.expand_path('../boot', __FILE__) Rails::Initializer.run do |config| # Settings in config/environments/* take precedence over those specified here. @@ -7,13 +6,7 @@ Rails::Initializer.run do |config| # -- all .rb files in that directory are automatically loaded. # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{RAILS_ROOT}/extras ) - - # Specify gems that this application depends on and have them installed with rake gems:install - # config.gem "bj" - # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" - # config.gem "sqlite3-ruby", :lib => "sqlite3" - # config.gem "aws-s3", :lib => "aws/s3" + # 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 @@ -45,4 +38,4 @@ Rails::Initializer.run do |config| # g.template_engine :erb # g.test_framework :test_unit, :fixture => true # end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb index 6e0e2279cd..5aa49ca5e6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -1,148 +1,16 @@ -# Don't change this file! -# Configure your app in config/environment.rb and config/environments/*.rb - -RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) - -module Rails - # Mark the version of Rails that generated the boot.rb file. This is - # a temporary solution and will most likely be removed as Rails 3.0 - # comes closer. - BOOTSTRAP_VERSION = "3.0" - - class << self - def boot! - unless booted? - preinitialize - pick_boot.run - end - end - - def booted? - defined? Rails::Initializer - end - - def pick_boot - (vendor_rails? ? VendorBoot : GemBoot).new - end - - def vendor_rails? - File.exist?("#{RAILS_ROOT}/vendor/rails") - end - - def preinitialize - load(preinitializer_path) if File.exist?(preinitializer_path) - end - - def preinitializer_path - "#{RAILS_ROOT}/config/preinitializer.rb" - end - end - - class Boot - def run - set_load_paths - load_initializer - end - - def set_load_paths - %w( - actionmailer/lib - actionpack/lib - activemodel/lib - activerecord/lib - activeresource/lib - activesupport/lib - railties/lib - railties - ).reverse_each do |path| - path = "#{framework_root_path}/#{path}" - $LOAD_PATH.unshift(path) if File.directory?(path) - $LOAD_PATH.uniq! - end - end - - def framework_root_path - defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{RAILS_ROOT}/vendor/rails" - end +# Use Bundler (preferred) +environment = File.expand_path('../../vendor/gems/environment', __FILE__) +if File.exist?("#{environment}.rb") + require environment + +# Use 2.x style vendor/rails and RubyGems +else + vendor_rails = File.expand_path('../../vendor/rails', __FILE__) + if File.exist?(vendor_rails) + Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) } end - class VendorBoot < Boot - def load_initializer - require "rails" - install_gem_spec_stubs - Rails::GemDependency.add_frozen_gem_path - end - - def install_gem_spec_stubs - begin; require "rubygems"; rescue LoadError; return; end - - %w(rails activesupport activerecord actionpack actionmailer activeresource).each do |stub| - Gem.loaded_specs[stub] ||= Gem::Specification.new do |s| - s.name = stub - s.version = Rails::VERSION::STRING - s.loaded_from = "" - end - end - end - end - - class GemBoot < Boot - def load_initializer - self.class.load_rubygems - load_rails_gem - require 'rails' - end - - def load_rails_gem - if version = self.class.gem_version - gem 'rails', version - else - gem 'rails' - end - rescue Gem::LoadError => load_error - $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) - exit 1 - end - - class << self - def rubygems_version - Gem::RubyGemsVersion rescue nil - end - - def gem_version - if defined? RAILS_GEM_VERSION - RAILS_GEM_VERSION - elsif ENV.include?('RAILS_GEM_VERSION') - ENV['RAILS_GEM_VERSION'] - else - parse_gem_version(read_environment_rb) - end - end - - def load_rubygems - min_version = '1.3.2' - require 'rubygems' - unless rubygems_version >= min_version - $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) - exit 1 - end - - rescue LoadError - $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) - exit 1 - end - - def parse_gem_version(text) - $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ - end - - private - def read_environment_rb - File.read("#{RAILS_ROOT}/config/environment.rb") - end - end - end + require 'rubygems' end -# All that for this: -Rails.boot! +require 'rails' 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 fcf4eddb00..0bb191f205 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -1,9 +1,5 @@ -# Be sure to restart your server when you modify this file - -# Specifies gem version of Rails to use when vendor/rails is not present -<%= '# ' if options[:freeze] %>RAILS_GEM_VERSION = '<%= Rails::VERSION::STRING %>' unless defined? RAILS_GEM_VERSION - # Load the rails application -require File.expand_path(File.join(File.dirname(__FILE__), 'application')) +require File.expand_path('../application', __FILE__) + # Initialize the rails application -Rails.application.new +Rails.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/script/about.tt b/railties/lib/rails/generators/rails/app/templates/script/about.tt index 90a320297e..7639d4040f 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/about.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/about.tt @@ -1,4 +1,4 @@ <%= shebang %> -require File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/environment', __FILE__) $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" require 'rails/commands/about' diff --git a/railties/lib/rails/generators/rails/app/templates/script/generate.tt b/railties/lib/rails/generators/rails/app/templates/script/generate.tt index 2a9d219531..26f029c6a6 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/generate.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/generate.tt @@ -1,3 +1,3 @@ <%= shebang %> -require File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/environment', __FILE__) require 'rails/commands/generate' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt index 90aca9b4e9..9ebc4c92fc 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt @@ -1,3 +1,3 @@ <%= shebang %> -require File.expand_path('../../../config/application', __FILE__) +require File.expand_path('../../../config/environment', __FILE__) require 'rails/commands/performance/benchmarker' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt index 39569b4e70..5f4c763f9d 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt @@ -1,3 +1,3 @@ <%= shebang %> -require File.expand_path('../../../config/application', __FILE__) +require File.expand_path('../../../config/environment', __FILE__) require 'rails/commands/performance/profiler' diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner.tt b/railties/lib/rails/generators/rails/app/templates/script/runner.tt index 872ec07a1e..34ad7c18eb 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/runner.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/runner.tt @@ -1,3 +1,3 @@ <%= shebang %> -require File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/environment', __FILE__) require 'rails/commands/runner' diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index d4b0d4b945..0385581083 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -1,3 +1,5 @@ +require 'rails/generators/active_model' + module Rails module Generators # Deal with controller names on scaffold and add some helpers to deal with @@ -47,20 +49,11 @@ module Rails raise "You need to have :orm as class option to invoke orm_class and orm_instance" end - active_model = "#{options[:orm].to_s.classify}::Generators::ActiveModel" - - # If the orm was not loaded, try to load it at "generators/orm", - # for example "generators/active_record" or "generators/sequel". begin - klass = active_model.constantize - rescue NameError - require "rails/generators/#{options[:orm]}" + "#{options[:orm].to_s.classify}::Generators::ActiveModel".constantize + rescue NameError => e + Rails::Generators::ActiveModel end - - # Try once again after loading the file with success. - klass ||= active_model.constantize - rescue Exception => e - raise Error, "Could not load #{active_model}, skipping controller. Error: #{e.message}." end end diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index 4bd5088207..c491d5e012 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -1,78 +1,86 @@ module Rails module Initializable - - # A collection of initializers - class Collection - def initialize(context) - @context = context - @keys = [] - @values = {} - @ran = false + def self.included(klass) + klass.instance_eval do + extend Rails::Initializable + extend Rails::Initializable::ClassMethodsWhenIncluded + include Rails::Initializable::InstanceMethodsWhenIncluded end + end - def run - return self if @ran - each do |key, initializer| - @context.class_eval(&initializer.block) - end - @ran = true - self - end + def self.extended(klass) + klass.extend Initializer + end - def [](key) - keys, values = merge_with_parent - values[key.to_sym] + class Collection < Array + def initialize(klasses) + klasses.each do |klass| + (klass.added_initializers || []).each do |initializer| + index = if initializer.before + index_for(initializer.before) + elsif initializer.after + index_for(initializer.after) + 1 + else + length + end + + insert(index, initializer) + end + end end - def []=(key, value) - key = key.to_sym - @keys |= [key] - @values[key] = value + def index_for(name) + inst = find {|i| i.name == name } + inst && index(inst) end - def each - keys, values = merge_with_parent - keys.each { |k| yield k, values[k] } - self - end + end - protected + attr_reader :added_initializers - attr_reader :keys, :values + # When you include Rails::Initializable, this method will be on instances + # of the class included into. When you extend it, it will be on the + # class or module itself. + # + # The #initializers method is set up to return the right list of + # initializers for the context in question. + def run_initializers + return if @_initialized - private + initializers.each {|initializer| instance_eval(&initializer.block) } - def merge_with_parent - keys, values = [], {} + @_initialized = true + end - if @context.is_a?(Class) && @context.superclass.is_a?(Initializable) - parent = @context.superclass.initializers - keys, values = parent.keys, parent.values - end + module Initializer + Initializer = Struct.new(:name, :before, :after, :block, :global) - values = values.merge(@values) - return keys | @keys, values + def all_initializers + klasses = ancestors.select {|klass| klass.is_a?(Initializable) }.reverse + initializers = Collection.new(klasses) end - end - - class Initializer - attr_reader :name, :options, :block + alias initializers all_initializers - def initialize(name, options = {}, &block) - @name, @options, @block = name, options, block + def initializer(name, options = {}, &block) + @added_initializers ||= [] + @added_initializers << + Initializer.new(name, options[:before], options[:after], block, options[:global]) end end - def initializer(name, options = {}, &block) - @initializers ||= Collection.new(self) - @initializers[name] = Initializer.new(name, options, &block) - end + module ClassMethodsWhenIncluded + def initializers + all_initializers.select {|i| i.global == true } + end - def initializers - @initializers ||= Collection.new(self) end + module InstanceMethodsWhenIncluded + def initializers + self.class.all_initializers.reject {|i| i.global == true } + end + end end extend Initializable diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb index bb86f70da6..2ad1e52746 100644 --- a/railties/lib/rails/initializer.rb +++ b/railties/lib/rails/initializer.rb @@ -4,11 +4,11 @@ require 'rails/initializable' require 'rails/application' require 'rails/railties_path' require 'rails/version' -require 'rails/gem_dependency' require 'rails/rack' require 'rails/paths' require 'rails/core' require 'rails/configuration' +require 'rails/deprecation' RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb index 0d16cbd7c3..4808c6ad57 100644 --- a/railties/lib/rails/plugin/loader.rb +++ b/railties/lib/rails/plugin/loader.rb @@ -147,7 +147,7 @@ module Rails end def application_lib_index - $LOAD_PATH.index(File.join(RAILS_ROOT, 'lib')) || 0 + $LOAD_PATH.index(File.join(Rails.root, 'lib')) || 0 end def enabled?(plugin) diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb index 1057c004e0..56cbaf37c6 100644 --- a/railties/lib/rails/plugin/locator.rb +++ b/railties/lib/rails/plugin/locator.rb @@ -78,7 +78,7 @@ module Rails # a <tt>rails/init.rb</tt> file. class GemLocator < Locator def plugins - gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem } + gem_index = {} specs = gem_index.keys specs += Gem.loaded_specs.values.select do |spec| spec.loaded_from && # prune stubs diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index aad965306c..82113a297c 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -6,7 +6,6 @@ $VERBOSE = nil databases documentation framework - gems log middleware misc @@ -20,5 +19,5 @@ 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 } +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/databases.rake b/railties/lib/rails/tasks/databases.rake index ed015e7a67..a35a6c156b 100644 --- a/railties/lib/rails/tasks/databases.rake +++ b/railties/lib/rails/tasks/databases.rake @@ -283,7 +283,7 @@ namespace :db do desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" task :dump => :environment do require 'active_record/schema_dumper' - File.open(ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb", "w") do |file| + File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end Rake::Task["db:schema:dump"].reenable @@ -291,11 +291,11 @@ namespace :db do desc "Load a schema.rb file into the database" task :load => :environment do - file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb" + file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" if File.exists?(file) load(file) else - abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} end end end @@ -307,7 +307,7 @@ namespace :db do case abcs[RAILS_ENV]["adapter"] when "mysql", "oci", "oracle" ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) - File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } when "postgresql" ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] @@ -327,13 +327,13 @@ namespace :db do when "firebird" set_firebird_env(abcs[RAILS_ENV]) db_string = firebird_db_string(abcs[RAILS_ENV]) - sh "isql -a #{db_string} > #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql" + sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql" else raise "Task not supported by '#{abcs["test"]["adapter"]}'" end if ActiveRecord::Base.connection.supports_migrations? - File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } end end end @@ -356,28 +356,28 @@ namespace :db do when "mysql" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end when "postgresql" ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` when "sqlite", "sqlite3" dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - `#{abcs["test"]["adapter"]} #{dbfile} < #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql` + `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql` when "sqlserver" `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) - IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end when "firebird" set_firebird_env(abcs["test"]) db_string = firebird_db_string(abcs["test"]) - sh "isql -i #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{db_string}" + sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}" else raise "Task not supported by '#{abcs["test"]["adapter"]}'" end @@ -446,7 +446,7 @@ def drop_database(config) when /^sqlite/ require 'pathname' path = Pathname.new(config['database']) - file = path.absolute? ? path.to_s : File.join(RAILS_ROOT, path) + file = path.absolute? ? path.to_s : File.join(Rails.root, path) FileUtils.rm(file) when 'postgresql' diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 16dd0af44e..1611d1d94d 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -86,7 +86,7 @@ namespace :rails do template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} require 'generators' - generator = Rails::Generators::App.new [ RAILS_ROOT ], {}, :destination_root => RAILS_ROOT + generator = Rails::Generators::App.new [ Rails.root ], {}, :destination_root => Rails.root generator.apply template, :verbose => false end @@ -96,15 +96,10 @@ namespace :rails do require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, - :destination_root => RAILS_ROOT + :destination_root => Rails.root generator.invoke(method) end - desc "Update config/boot.rb from your current rails install" - task :configs do - invoke_from_app_generator :create_boot_file - end - desc "Update Prototype javascripts from your current rails install" task :javascripts do invoke_from_app_generator :create_prototype_files @@ -117,8 +112,8 @@ namespace :rails do desc "Rename application.rb to application_controller.rb" task :application_controller do - old_style = RAILS_ROOT + '/app/controllers/application.rb' - new_style = RAILS_ROOT + '/app/controllers/application_controller.rb' + old_style = Rails.root + '/app/controllers/application.rb' + new_style = Rails.root + '/app/controllers/application_controller.rb' if File.exists?(old_style) && !File.exists?(new_style) FileUtils.mv(old_style, new_style) puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary" diff --git a/railties/lib/rails/tasks/gems.rake b/railties/lib/rails/tasks/gems.rake deleted file mode 100644 index f1c34c7cca..0000000000 --- a/railties/lib/rails/tasks/gems.rake +++ /dev/null @@ -1,78 +0,0 @@ -desc "List the gems that this rails application depends on" -task :gems => 'gems:base' do - Rails.configuration.gems.each do |gem| - print_gem_status(gem) - end - puts - puts "I = Installed" - puts "F = Frozen" - puts "R = Framework (loaded before rails starts)" -end - -namespace :gems do - task :base do - $gems_rake_task = true - require 'rubygems' - require 'rubygems/gem_runner' - Rake::Task[:environment].invoke - end - - desc "Build any native extensions for unpacked gems" - task :build do - $gems_build_rake_task = true - frozen_gems.each { |gem| gem.build } - end - - namespace :build do - desc "Force the build of all gems" - task :force do - $gems_build_rake_task = true - frozen_gems.each { |gem| gem.build(:force => true) } - end - end - - desc "Installs all required gems." - task :install => :base do - current_gems.each { |gem| gem.install } - end - - desc "Unpacks all required gems into vendor/gems." - task :unpack => :install do - current_gems.each { |gem| gem.unpack } - end - - namespace :unpack do - desc "Unpacks all required gems and their dependencies into vendor/gems." - task :dependencies => :install do - current_gems.each { |gem| gem.unpack(:recursive => true) } - end - end - - desc "Regenerate gem specifications in correct format." - task :refresh_specs do - frozen_gems(false).each { |gem| gem.refresh } - end -end - -def current_gems - gems = Rails.configuration.gems - gems = gems.select { |gem| gem.name == ENV['GEM'] } unless ENV['GEM'].blank? - gems -end - -def frozen_gems(load_specs=true) - Dir[File.join(RAILS_ROOT, 'vendor', 'gems', '*-*')].map do |gem_dir| - Rails::GemDependency.from_directory_name(gem_dir, load_specs) - end -end - -def print_gem_status(gem, indent=1) - code = case - when gem.framework_gem? then 'R' - when gem.frozen? then 'F' - when gem.installed? then 'I' - else ' ' - end - puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" - gem.dependencies.each { |g| print_gem_status(g, indent+1) } -end diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index fb2fc31dc1..7f244ebaed 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -1,7 +1,7 @@ task :default => :test task :environment do $rails_rake_task = true - require(File.join(RAILS_ROOT, 'config', 'environment')) + require(File.join(Rails.root, 'config', 'environment')) end task :rails_env do diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index abbf3258c1..2395d73b2f 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -3,16 +3,13 @@ task :routes => :environment do all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes routes = all_routes.collect do |route| name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s - verb = route.conditions[:method].to_s.upcase - segs = route.segments.inject("") { |str,s| str << s.to_s } - segs.chop! if segs.length > 1 reqs = route.requirements.empty? ? "" : route.requirements.inspect - {:name => name, :verb => verb, :segs => segs, :reqs => reqs} + {:name => name, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end name_width = routes.collect {|r| r[:name]}.collect {|n| n.length}.max verb_width = routes.collect {|r| r[:verb]}.collect {|v| v.length}.max - segs_width = routes.collect {|r| r[:segs]}.collect {|s| s.length}.max + path_width = routes.collect {|r| r[:path]}.collect {|s| s.length}.max routes.each do |r| - puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}" + puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" end end diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 2dcc7bdf9d..40f8c1034a 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -1,14 +1,13 @@ STATS_DIRECTORIES = [ %w(Controllers app/controllers), - %w(Helpers app/helpers), + %w(Helpers app/helpers), %w(Models app/models), %w(Libraries lib/), %w(APIs app/apis), %w(Integration\ tests test/integration), %w(Functional\ tests test/functional), %w(Unit\ tests test/unit) - -].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } +].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } desc "Report code statistics (KLOCs, etc) from the application" task :stats do diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 8bd4475c7b..9f6c42945f 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -17,7 +17,7 @@ if defined?(ActiveRecord) class ActiveSupport::TestCase include ActiveRecord::TestFixtures - self.fixture_path = "#{RAILS_ROOT}/test/fixtures/" + self.fixture_path = "#{Rails.root}/test/fixtures/" self.use_instantiated_fixtures = false self.use_transactional_fixtures = true end diff --git a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor b/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor deleted file mode 100755 index 50c7410d80..0000000000 --- a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require 'ruby2ruby' -require 'parse_tree' -if Ruby2Ruby::VERSION >= "1.2.0" - require 'parse_tree_extensions' -end -require 'rake' - -input = ARGV[0] || 'Rakefile' -output = ARGV[1] || 'Thorfile' - -$requires = [] - -module Kernel - def require_with_record(file) - $requires << file if caller[1] =~ /rake2thor:/ - require_without_record file - end - alias_method :require_without_record, :require - alias_method :require, :require_with_record -end - -load input - -@private_methods = [] - -def file_task_name(name) - "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_') -end - -def method_for_task(task) - file_task = task.is_a?(Rake::FileTask) - comment = task.instance_variable_get('@comment') - prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?)) - actions = task.instance_variable_get('@actions') - name = task.name.gsub(/^([^:]+:)+/, '') - name = file_task_name(name) if file_task - meth = '' - - meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment - meth << "def #{name}\n" - - meth << prereqs.map do |pre| - pre = pre.to_s - pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask) - ' ' + pre - end.join("\n") - - meth << "\n\n" unless prereqs.empty? || actions.empty? - - meth << actions.map do |act| - act = act.to_ruby - unless act.gsub!(/^proc \{ \|(\w+)\|\n/, - " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n") - act.gsub!(/^proc \{\n/, '') - end - act.gsub(/\n\}$/, '') - end.join("\n") - - meth << "\nend" - - if file_task - @private_methods << meth - return - end - - meth -end - -body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") - -unless @private_methods.empty? - body << "\n\n private\n\n" - body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") -end - -requires = $requires.map { |r| "require #{r.inspect}" }.join("\n") - -File.open(output, 'w') { |f| f.write(<<END.lstrip) } -#{requires} - -class Default < Thor -#{body} -end -END diff --git a/railties/lib/rails/vendor/thor-0.11.6/bin/thor b/railties/lib/rails/vendor/thor-0.11.6/bin/thor deleted file mode 100755 index eaf849fb4a..0000000000 --- a/railties/lib/rails/vendor/thor-0.11.6/bin/thor +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby -# -*- mode: ruby -*- - -require File.join(File.dirname(__FILE__), '..', 'lib', 'thor') -require 'thor/runner' - -Thor::Runner.start diff --git a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.11.8/CHANGELOG.rdoc index dba25b7205..dba25b7205 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc +++ b/railties/lib/rails/vendor/thor-0.11.8/CHANGELOG.rdoc diff --git a/railties/lib/rails/vendor/thor-0.11.6/LICENSE b/railties/lib/rails/vendor/thor-0.11.8/LICENSE index 98722da459..98722da459 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/LICENSE +++ b/railties/lib/rails/vendor/thor-0.11.8/LICENSE diff --git a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc b/railties/lib/rails/vendor/thor-0.11.8/README.rdoc index f1106f02b6..f1106f02b6 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc +++ b/railties/lib/rails/vendor/thor-0.11.8/README.rdoc diff --git a/railties/lib/rails/vendor/thor-0.11.8/Thorfile b/railties/lib/rails/vendor/thor-0.11.8/Thorfile new file mode 100644 index 0000000000..f71a1e57e2 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.11.8/Thorfile @@ -0,0 +1,63 @@ +# enconding: utf-8 + +require File.join(File.dirname(__FILE__), "lib", "thor", "version") +require 'thor/rake_compat' +require 'spec/rake/spectask' +require 'rdoc/task' + +GEM_NAME = 'thor' +EXTRA_RDOC_FILES = ["README.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"] + +class Default < Thor + include Thor::RakeCompat + + Spec::Rake::SpecTask.new(:spec) do |t| + t.libs << 'lib' + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/**/*_spec.rb'] + end + + Spec::Rake::SpecTask.new(:rcov) do |t| + t.libs << 'lib' + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/**/*_spec.rb'] + t.rcov = true + t.rcov_dir = "rcov" + end + + RDoc::Task.new do |rdoc| + rdoc.main = "README.rdoc" + rdoc.rdoc_dir = "rdoc" + rdoc.title = GEM_NAME + rdoc.rdoc_files.include(*EXTRA_RDOC_FILES) + rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.options << '--line-numbers' << '--inline-source' + end + + begin + require 'jeweler' + Jeweler::Tasks.new do |s| + s.name = GEM_NAME + s.version = Thor::VERSION + s.rubyforge_project = "textmate" + s.platform = Gem::Platform::RUBY + s.summary = "A scripting framework that replaces rake, sake and rubigen" + s.email = "ruby-thor@googlegroups.com" + s.homepage = "http://yehudakatz.com" + s.description = "A scripting framework that replaces rake, sake and rubigen" + s.authors = ['Yehuda Katz', 'José Valim'] + s.has_rdoc = true + s.extra_rdoc_files = EXTRA_RDOC_FILES + s.require_path = 'lib' + s.bindir = "bin" + s.executables = %w( thor rake2thor ) + s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*") + s.files.exclude 'spec/sandbox/**/*' + s.test_files.exclude 'spec/sandbox/**/*' + end + + Jeweler::RubyforgeTasks.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 +end diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor.rb index 3b45c4e9b7..68944f140d 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor.rb @@ -1,4 +1,3 @@ -$:.unshift File.expand_path(File.dirname(__FILE__)) require 'thor/base' require 'thor/group' require 'thor/actions' diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions.rb index d561ccb2aa..d561ccb2aa 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/create_file.rb index 8f6badee27..8f6badee27 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/create_file.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/directory.rb index be5eb822ac..063ac57406 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/directory.rb @@ -73,7 +73,9 @@ class Thor case file_source when /\.empty_directory$/ - base.empty_directory(File.dirname(file_destination), config) + dirname = File.dirname(file_destination).gsub(/\/\.$/, '') + next if dirname == given_destination + base.empty_directory(dirname, config) when /\.tt$/ base.template(file_source, file_destination[0..-4], config) else diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/empty_directory.rb index 03c1fe4af1..03c1fe4af1 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/empty_directory.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/file_manipulation.rb index d77d90d448..d77d90d448 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/file_manipulation.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/inject_into_file.rb index 0636ec6591..0636ec6591 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/actions/inject_into_file.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/base.rb index 700d794123..700d794123 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/base.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/hash_with_indifferent_access.rb index 78bc5cf4bf..78bc5cf4bf 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/hash_with_indifferent_access.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/ordered_hash.rb index 27fea5bb35..27fea5bb35 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/core_ext/ordered_hash.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/error.rb index f9b31a35d1..f9b31a35d1 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/error.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/group.rb index 1e59df2313..1e59df2313 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/group.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/invocation.rb index 32e6a72454..32e6a72454 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/invocation.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser.rb index 57a3f6e1a5..57a3f6e1a5 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/argument.rb index aa8ace4719..aa8ace4719 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/argument.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/arguments.rb index fb5d965e06..fb5d965e06 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/arguments.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/option.rb index 9e40ec73fa..9e40ec73fa 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/option.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/options.rb index 75092308b5..75092308b5 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/parser/options.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/rake_compat.rb index 3ab6bb21f5..0d0757fdda 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/rake_compat.rb @@ -22,7 +22,8 @@ class Thor def self.included(base) # Hack. Make rakefile point to invoker, so rdoc task is generated properly. - Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1]) + rakefile = File.basename(caller[0].match(/(.*):\d+/)[1]) + Rake.application.instance_variable_set(:@rakefile, rakefile) self.rake_classes << base end end @@ -43,11 +44,9 @@ class Object #:nodoc: description.strip! klass.desc description, task.comment || non_namespaced_name - klass.class_eval <<-METHOD - def #{non_namespaced_name}(#{task.arg_names.join(', ')}) - Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')}) - end - METHOD + klass.send :define_method, non_namespaced_name do |*args| + Rake::Task[task.name.to_sym].invoke(*args) + end end task diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/runner.rb index 43da09b336..9dc70ea069 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/runner.rb @@ -215,7 +215,7 @@ class Thor::Runner < Thor #:nodoc: # 5. c:\ <-- no Thorfiles found! # def thorfiles(relevant_to=nil, skip_lookup=false) - # Deal with deprecated thor when :namespaces: is available as constants + # TODO Remove this dealing with deprecated thor when :namespaces: is available as constants save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml) thorfiles = [] diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell.rb index 0d3f4d5951..1dc8f0e5b4 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell.rb @@ -1,11 +1,17 @@ +require 'rbconfig' require 'thor/shell/color' class Thor module Base - # Returns the shell used in all Thor classes. Default to color one. + # Returns the shell used in all Thor classes. If you are in a Unix platform + # it will use a colored log, otherwise it will use a basic one without color. # def self.shell - @shell ||= Thor::Shell::Color + @shell ||= if Config::CONFIG['host_os'] =~ /mswin|mingw/ + Thor::Shell::Basic + else + Thor::Shell::Color + end end # Sets the shell used in all Thor classes. diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/basic.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/basic.rb index ea9665380b..ea9665380b 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/basic.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/basic.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/color.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/color.rb index 24704f7885..24704f7885 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/color.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/shell/color.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/task.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/task.rb index 91c7564d3f..91c7564d3f 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/task.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/task.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/util.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/util.rb index fd820d7462..ebae0a3193 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/util.rb +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/util.rb @@ -209,7 +209,7 @@ class Thor # Returns the root where thor files are located, dependending on the OS. # def self.thor_root - File.join(user_home, ".thor") + File.join(user_home, ".thor").gsub(/\\/, '/') end # Returns the files in the thor root. On Windows thor_root will be something @@ -220,7 +220,7 @@ class Thor # If we don't #gsub the \ character, Dir.glob will fail. # def self.thor_root_glob - files = Dir["#{thor_root.gsub(/\\/, '/')}/*"] + files = Dir["#{thor_root}/*"] files.map! do |file| File.directory?(file) ? File.join(file, "main.thor") : file diff --git a/railties/lib/rails/vendor/thor-0.11.8/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/version.rb new file mode 100644 index 0000000000..885230fac4 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.11.8/lib/thor/version.rb @@ -0,0 +1,3 @@ +class Thor + VERSION = "0.11.8".freeze +end diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb deleted file mode 100644 index 5b7721f303..0000000000 --- a/railties/lib/rails/vendor_gem_source_index.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'rubygems' -require 'yaml' - -module Rails - - class VendorGemSourceIndex - # VendorGemSourceIndex acts as a proxy for the Gem source index, allowing - # gems to be loaded from vendor/gems. Rather than the standard gem repository format, - # vendor/gems contains unpacked gems, with YAML specifications in .specification in - # each gem directory. - include Enumerable - - attr_reader :installed_source_index - attr_reader :vendor_source_index - - @@silence_spec_warnings = false - - def self.silence_spec_warnings - @@silence_spec_warnings - end - - def self.silence_spec_warnings=(v) - @@silence_spec_warnings = v - end - - def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path) - @installed_source_index = installed_index - @vendor_dir = vendor_dir - refresh! - end - - def refresh! - # reload the installed gems - @installed_source_index.refresh! - vendor_gems = {} - - # handle vendor Rails gems - they are identified by having loaded_from set to "" - # we add them manually to the list, so that other gems can find them via dependencies - Gem.loaded_specs.each do |n, s| - next unless s.loaded_from.empty? - vendor_gems[s.full_name] = s - end - - # load specifications from vendor/gems - Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d| - dir_name = File.basename(d) - dir_version = version_for_dir(dir_name) - spec = load_specification(d) - if spec - if spec.full_name != dir_name - # mismatched directory name and gem spec - produced by 2.1.0-era unpack code - if dir_version - # fix the spec version - this is not optimal (spec.files may be wrong) - # but it's better than breaking apps. Complain to remind users to get correct specs. - # use ActiveSupport::Deprecation.warn, as the logger is not set yet - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has a mismatched specification file."+ - " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings - spec.version = dir_version - else - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems is not in a versioned directory"+ - "(should be #{spec.full_name}).") unless @@silence_spec_warnings - # continue, assume everything is OK - end - end - else - # no spec - produced by early-2008 unpack code - # emulate old behavior, and complain. - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has no specification file."+ - " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings - if dir_version - spec = Gem::Specification.new - spec.version = dir_version - spec.require_paths = ['lib'] - ext_path = File.join(d, 'ext') - spec.require_paths << 'ext' if File.exist?(ext_path) - spec.name = /^(.*)-[^-]+$/.match(dir_name)[1] - files = ['lib'] - # set files to everything in lib/ - files += Dir[File.join(d, 'lib', '*')].map { |v| v.gsub(/^#{d}\//, '') } - files += Dir[File.join(d, 'ext', '*')].map { |v| v.gsub(/^#{d}\//, '') } if ext_path - spec.files = files - else - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+ - " Giving up.") unless @@silence_spec_warnings - next - end - end - spec.loaded_from = File.join(d, '.specification') - # finally, swap out full_gem_path - # it would be better to use a Gem::Specification subclass, but the YAML loads an explicit class - class << spec - def full_gem_path - path = File.join installation_path, full_name - return path if File.directory? path - File.join installation_path, original_name - end - end - vendor_gems[File.basename(d)] = spec - end - @vendor_source_index = Gem::SourceIndex.new(vendor_gems) - end - - def version_for_dir(d) - matches = /-([^-]+)$/.match(d) - Gem::Version.new(matches[1]) if matches - end - - def load_specification(gem_dir) - spec_file = File.join(gem_dir, '.specification') - YAML.load_file(spec_file) if File.exist?(spec_file) - end - - def find_name(*args) - @installed_source_index.find_name(*args) + @vendor_source_index.find_name(*args) - end - - def search(*args) - # look for vendor gems, and then installed gems - later elements take priority - @installed_source_index.search(*args) + @vendor_source_index.search(*args) - end - - def each(&block) - @vendor_source_index.each(&block) - @installed_source_index.each(&block) - end - - def add_spec(spec) - @vendor_source_index.add_spec spec - end - - def remove_spec(spec) - @vendor_source_index.remove_spec spec - end - - def size - @vendor_source_index.size + @installed_source_index.size - end - - end -end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 551468b3e8..47013d7797 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -1,28 +1,27 @@ ORIG_ARGV = ARGV.dup -require 'rubygems' -gem 'rack', '~> 1.0.0' -gem 'rack-test', '~> 0.5.0' +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + %w(activesupport activemodel activerecord actionpack actionmailer activeresource railties).each do |lib| + $:.unshift "#{root}/#{lib}/lib" + end +end -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" -$:.unshift File.dirname(__FILE__) + "/../../activerecord/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib" -$:.unshift File.dirname(__FILE__) + "/../../activeresource/lib" -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info" +$:.unshift "#{root}/railties/builtin/rails_info" require 'stringio' require 'test/unit' +require 'fileutils' require 'active_support' require 'active_support/core_ext/logger' require 'active_support/test_case' require 'action_controller' +require 'rails' -if defined?(RAILS_ROOT) - RAILS_ROOT.replace File.dirname(__FILE__) -else - RAILS_ROOT = File.dirname(__FILE__) +Rails::Initializer.run do |config| + config.root = File.dirname(__FILE__) end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index d90582d3db..a3e1916494 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -7,12 +7,11 @@ module ApplicationTests def setup build_app boot_rails - Object.send(:remove_const, :RAILS_ROOT) end test "the application root is set correctly" do require "#{app_path}/config/environment" - assert_equal app_path, Rails.application.root + assert_equal Pathname.new(app_path), Rails.application.root end test "the application root can be set" do @@ -22,7 +21,7 @@ module ApplicationTests config.root = '#{app_path}/hello' RUBY require "#{app_path}/config/environment" - assert_equal "#{app_path}/hello", Rails.application.root + assert_equal Pathname.new("#{app_path}/hello"), Rails.application.root end test "the application root is detected as where config.ru is located" do @@ -31,7 +30,7 @@ module ApplicationTests RUBY FileUtils.mv "#{app_path}/config.ru", "#{app_path}/config/config.ru" require "#{app_path}/config/environment" - assert_equal "#{app_path}/config", Rails.application.root + assert_equal Pathname.new("#{app_path}/config"), Rails.application.root end test "the application root is Dir.pwd if there is no config.ru" do @@ -42,7 +41,7 @@ module ApplicationTests Dir.chdir("#{app_path}/app") do require "#{app_path}/config/environment" - assert_equal "#{app_path}/app", Rails.application.root + assert_equal Pathname.new("#{app_path}/app"), Rails.application.root end end end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index c664b61137..445a867c85 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -7,6 +7,7 @@ module ApplicationTests def setup build_app boot_rails + require "rails" require "rails/generators" end @@ -37,12 +38,13 @@ module ApplicationTests 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 # Initialize the application - Rails.application.new + Rails.initialize! assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework] @@ -50,10 +52,11 @@ module ApplicationTests test "generators no color on initialization" do Rails::Initializer.run do |c| + c.frameworks = [] c.generators.colorize_logging = false end # Initialize the application - Rails.application.new + Rails.initialize! assert_equal Thor::Base.shell, Thor::Shell::Basic end diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb index 76486d8f2c..719520bf68 100644 --- a/railties/test/application/initializer_test.rb +++ b/railties/test/application/initializer_test.rb @@ -7,36 +7,42 @@ module ApplicationTests def setup build_app boot_rails + require "rails" end test "initializing an application initializes rails" do - class MyApp < Rails::Application ; end + Rails::Initializer.run do |config| + config.root = app_path + end if RUBY_VERSION < '1.9' $KCODE = '' - MyApp.new + Rails.initialize! assert_equal 'UTF8', $KCODE else Encoding.default_external = Encoding::US_ASCII - MyApp.new + 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 - class MyApp < Rails::Application ; end + Rails::Initializer.run do |config| + config.root = app_path + end - MyApp.new + Rails.initialize! assert $:.include?("#{app_path}/app/models") end test "adding an unknown framework raises an error" do - class MyApp < Rails::Application + Rails::Initializer.run do |config| + config.root = app_path config.frameworks << :action_foo end assert_raises RuntimeError do - MyApp.new + Rails.initialize! end end @@ -49,10 +55,11 @@ module ApplicationTests ZOO Rails::Initializer.run do |config| + config.root = app_path config.eager_load_paths = "#{app_path}/lib" end - Rails.application.new + Rails.initialize! assert Zoo end @@ -60,44 +67,28 @@ 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 { } - Rails.application.new + Rails::Initializer.run { |config| config.root = app_path } + Rails.initialize! 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 config.frameworks = [] end - Rails.application.new + Rails.initialize! end end - test "action_pack is added to the load path if action_controller is required" do - Rails::Initializer.run do |config| - config.frameworks = [:action_controller] - end - Rails.application.new - - assert $:.include?("#{framework_path}/actionpack/lib") - end - - test "action_pack is added to the load path if action_view is required" do - Rails::Initializer.run do |config| - config.frameworks = [:action_view] - end - Rails.application.new - - assert $:.include?("#{framework_path}/actionpack/lib") - end - test "after_initialize block works correctly" do Rails::Initializer.run do |config| + config.root = app_path config.after_initialize { $test_after_initialize_block1 = "success" } config.after_initialize { $test_after_initialize_block2 = "congratulations" } end - Rails.application.new + Rails.initialize! assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 @@ -105,11 +96,12 @@ module ApplicationTests test "after_initialize block works correctly when no block is passed" do Rails::Initializer.run do |config| + 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.application.new + Rails.initialize! assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 @@ -118,9 +110,10 @@ module ApplicationTests # i18n test "setting another default locale" do Rails::Initializer.run do |config| + config.root = app_path config.i18n.default_locale = :de end - Rails.application.new + Rails.initialize! assert_equal :de, I18n.default_locale end @@ -128,18 +121,21 @@ module ApplicationTests 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 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 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 @@ -151,15 +147,17 @@ 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 config.action_controller.session_store = :cookie_store end - Rails.application.new + Rails.initialize! 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 @@ -167,9 +165,10 @@ module ApplicationTests 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.application.new + Rails.initialize! expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] middleware = Rails.application.config.middleware.map { |m| m.klass } @@ -178,10 +177,11 @@ module ApplicationTests 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.application.new + Rails.initialize! assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore) end @@ -189,20 +189,20 @@ module ApplicationTests # 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.application.new + Rails.initialize! assert_equal nil, ActionMailer::Base.template_root assert_equal [], ActionController::Base.view_paths end - # Rails root test - test "Rails.root == RAILS_ROOT" do - assert_equal RAILS_ROOT, Rails.root.to_s - 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 end end diff --git a/railties/test/application/load_test.rb b/railties/test/application/load_test.rb index 5c3d35fb16..3da51c4355 100644 --- a/railties/test/application/load_test.rb +++ b/railties/test/application/load_test.rb @@ -40,7 +40,7 @@ module ApplicationTests test "Rails.application is available after config.ru has been racked up" do rackup - assert Rails.application.new < Rails::Application + assert Rails.application < Rails::Application end # Passenger still uses AC::Dispatcher, so we need to diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb new file mode 100644 index 0000000000..62ed4f4ad4 --- /dev/null +++ b/railties/test/application/notifications_test.rb @@ -0,0 +1,46 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class NotificationsTest < Test::Unit::TestCase + 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 + end + end + + def setup + build_app + boot_rails + require "rails" + require "active_support/notifications" + Rails::Initializer.run do |c| + c.notifications.queue = MyQueue.new + c.notifications.subscribe(/listening/) do + puts "Cool" + end + 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/ } + end + end +end diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb index a4cf322139..80a19856d7 100644 --- a/railties/test/application/plugins_test.rb +++ b/railties/test/application/plugins_test.rb @@ -11,63 +11,68 @@ module ApplicationTests def setup build_app boot_rails + require "rails" @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" # Tmp hax to get tests working FileUtils.cp_r "#{File.dirname(__FILE__)}/../fixtures/plugins", "#{app_path}/vendor" end test "all plugins are loaded when registered plugin list is untouched" do - Rails::Initializer.run { } - Rails.application.new + Rails::Initializer.run { |c| c.root = app_path } + Rails.initialize! assert_plugins [ :a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby ], Rails.application.config.loaded_plugins, @failure_tip end test "no plugins are loaded if the configuration has an empty plugin list" do - Rails::Initializer.run { |c| c.plugins = [] } + Rails::Initializer.run { |c| c.root = app_path; c.plugins = [] } assert_plugins [], Rails.application.config.loaded_plugins end test "only the specified plugins are located in the order listed" do plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon] - Rails::Initializer.run { |c| c.plugins = plugin_names } - Rails.application.new + Rails::Initializer.run { |c| c.root = app_path; c.plugins = plugin_names } + Rails.initialize! assert_plugins plugin_names, Rails.application.config.loaded_plugins end test "all plugins loaded after all" do Rails::Initializer.run do |config| + config.root = app_path config.plugins = [:stubby, :all, :acts_as_chunky_bacon] end - Rails.application.new + Rails.initialize! assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], Rails.application.config.loaded_plugins, @failure_tip end test "plugin names may be strings" do plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] Rails::Initializer.run do |config| + config.root = app_path config.plugins = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] end - Rails.application.new + Rails.initialize! assert_plugins plugin_names, Rails.application.config.loaded_plugins, @failure_tip end test "all plugins loaded when all is used" do Rails::Initializer.run do |config| + config.root = app_path config.plugins = [:stubby, :acts_as_chunky_bacon, :all] end - Rails.application.new - + Rails.initialize! + assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], Rails.application.config.loaded_plugins, @failure_tip end test "all loaded plugins are added to the load paths" do Rails::Initializer.run do |config| + config.root = app_path config.plugins = [:stubby, :acts_as_chunky_bacon] end - Rails.application.new + Rails.initialize! assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/stubby/lib") assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/acts/acts_as_chunky_bacon/lib") @@ -75,11 +80,12 @@ module ApplicationTests test "registering a plugin name that does not exist raises a load error" do Rails::Initializer.run do |config| + config.root = app_path config.plugins = [:stubby, :acts_as_a_non_existant_plugin] end assert_raise(LoadError) do - Rails.application.new + Rails.initialize! end end @@ -89,9 +95,10 @@ module ApplicationTests begin Rails::Initializer.run do |config| + config.root = app_path config.plugins = [:stubby, :acts_as_chunky_bacon, :non_existant_plugin1, :non_existant_plugin2] end - Rails.application.new + Rails.initialize! flunk "Expected a LoadError but did not get one" rescue LoadError => e assert_plugins valid_plugins, Rails.application.config.loaded_plugins, @failure_tip diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index c3e4f970fe..4e273852e0 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -1,6 +1,4 @@ require 'abstract_unit' - -require 'rails/initializer' require 'rails/backtrace_cleaner' if defined? Test::Unit::Util::BacktraceFilter @@ -31,31 +29,26 @@ else $stderr.puts 'No BacktraceFilter for minitest' end -class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase - def setup - @cleaner = Rails::BacktraceCleaner.new - end - - test "should format installed gems correctly" do - @backtrace = [ "#{Gem.default_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] - end +if defined? Gem + class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase + def setup + @cleaner = Rails::BacktraceCleaner.new + end - test "should format installed gems not in Gem.default_dir correctly" do - @target_dir = Gem.path.detect { |p| p != Gem.default_dir } - # skip this test if default_dir is the only directory on Gem.path - if @target_dir - @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + test "should format installed gems correctly" do + @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] @result = @cleaner.clean(@backtrace) assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] end - end - test "should format vendor gems correctly" do - @backtrace = [ "#{Rails::GemDependency.unpacked_path}/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) - assert_equal "nosuchgem (1.2.3) [v] lib/foo.rb", @result[0] + test "should format installed gems not in Gem.default_dir correctly" do + @target_dir = Gem.path.detect { |p| p != Gem.default_dir } + # skip this test if default_dir is the only directory on Gem.path + if @target_dir + @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + end + end end - end diff --git a/railties/test/boot_test.rb b/railties/test/boot_test.rb deleted file mode 100644 index 1280d27ffe..0000000000 --- a/railties/test/boot_test.rb +++ /dev/null @@ -1,172 +0,0 @@ -require 'abstract_unit' -require 'rails/initializer' -require "#{File.dirname(__FILE__)}/../lib/rails/generators/rails/app/templates/config/boot" -require 'rails/gem_dependency' - -class BootTest < Test::Unit::TestCase - def test_boot_returns_if_booted - Rails.expects(:booted?).returns(true) - Rails.expects(:pick_boot).never - assert_nil Rails.boot! - end - - def test_boot_preinitializes_then_picks_and_runs_if_not_booted - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - Rails.expects(:pick_boot).returns(mock(:run => 'result')) - assert_equal 'result', Rails.boot! - end - - def test_preinitialize_does_not_raise_exception_if_preinitializer_file_does_not_exist - Rails.stubs(:preinitializer_path).returns('/there/is/no/such/file') - - assert_nothing_raised { Rails.preinitialize } - end - - def test_load_preinitializer_loads_preinitializer_file - Rails.stubs(:preinitializer_path).returns("#{File.dirname(__FILE__)}/fixtures/environment_with_constant.rb") - - assert_nil $initialize_test_set_from_env - Rails.preinitialize - assert_equal "success", $initialize_test_set_from_env - ensure - $initialize_test_set_from_env = nil - end - - def test_boot_vendor_rails_by_default - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(true) - Rails::VendorBoot.any_instance.expects(:run).returns('result') - assert_equal 'result', Rails.boot! - end - - def test_boot_gem_rails_otherwise - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(false) - Rails::GemBoot.any_instance.expects(:run).returns('result') - assert_equal 'result', Rails.boot! - end -end - -class VendorBootTest < Test::Unit::TestCase - include Rails - - def test_load_initializer_requires_from_vendor_rails - boot = VendorBoot.new - boot.expects(:require).with("rails") - boot.expects(:install_gem_spec_stubs) - Rails::GemDependency.expects(:add_frozen_gem_path) - boot.load_initializer - end -end - -class GemBootTest < Test::Unit::TestCase - include Rails - - def test_load_initializer_loads_rubygems_and_the_rails_gem - boot = GemBoot.new - GemBoot.expects(:load_rubygems) - boot.expects(:load_rails_gem) - boot.expects(:require).with('rails') - boot.load_initializer - end - - def test_load_rubygems_exits_with_error_if_missing - GemBoot.expects(:require).with('rubygems').raises(LoadError, 'missing rubygems') - STDERR.expects(:puts) - GemBoot.expects(:exit).with(1) - GemBoot.load_rubygems - end - - def test_load_rubygems_exits_with_error_if_too_old - GemBoot.stubs(:rubygems_version).returns('0.0.1') - GemBoot.expects(:require).with('rubygems').returns(true) - STDERR.expects(:puts) - GemBoot.expects(:exit).with(1) - GemBoot.load_rubygems - end - - def test_load_rails_gem_activates_specific_gem_if_version_given - GemBoot.stubs(:gem_version).returns('0.0.1') - - boot = GemBoot.new - boot.expects(:gem).with('rails', '0.0.1') - boot.load_rails_gem - end - - def test_load_rails_gem_activates_latest_gem_if_no_version_given - GemBoot.stubs(:gem_version).returns(nil) - - boot = GemBoot.new - boot.expects(:gem).with('rails') - boot.load_rails_gem - end - - def test_load_rails_gem_exits_with_error_if_missing - GemBoot.stubs(:gem_version).returns('0.0.1') - - boot = GemBoot.new - boot.expects(:gem).with('rails', '0.0.1').raises(Gem::LoadError, 'missing rails 0.0.1 gem') - STDERR.expects(:puts) - boot.expects(:exit).with(1) - boot.load_rails_gem - end -end - -class ParseGemVersionTest < Test::Unit::TestCase - def test_should_return_nil_if_no_lines_are_passed - assert_equal nil, parse('') - assert_equal nil, parse(nil) - end - - def test_should_accept_either_single_or_double_quotes - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse('RAILS_GEM_VERSION = "1.2.3"') - end - - def test_should_return_nil_if_no_lines_match - assert_equal nil, parse('nothing matches on this line\nor on this line') - end - - def test_should_parse_with_no_leading_space - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'") - end - - def test_should_parse_with_any_number_of_leading_spaces - assert_equal nil, parse([]) - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'") - end - - def test_should_ignore_unrelated_comments - assert_equal "1.2.3", parse("# comment\nRAILS_GEM_VERSION = '1.2.3'\n# comment") - end - - def test_should_ignore_commented_version_lines - assert_equal "1.2.3", parse("#RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse("# RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'\n# RAILS_GEM_VERSION = '9.8.7'") - end - - def test_should_allow_advanced_rubygems_version_specifications - # See http://rubygems.org/read/chapter/16 - assert_equal "=1.2.3", parse("RAILS_GEM_VERSION = '=1.2.3'") # equal sign - assert_equal "= 1.2.3", parse("RAILS_GEM_VERSION = '= 1.2.3'") # with space - assert_equal "!=1.2.3", parse("RAILS_GEM_VERSION = '!=1.2.3'") # not equal - assert_equal ">1.2.3", parse("RAILS_GEM_VERSION = '>1.2.3'") # greater than - assert_equal "<1.2.3", parse("RAILS_GEM_VERSION = '<1.2.3'") # less than - assert_equal ">=1.2.3", parse("RAILS_GEM_VERSION = '>=1.2.3'") # greater than or equal - assert_equal "<=1.2.3", parse("RAILS_GEM_VERSION = '<=1.2.3'") # less than or equal - assert_equal "~>1.2.3.0", parse("RAILS_GEM_VERSION = '~>1.2.3.0'") # approximately greater than - end - - private - def parse(text) - Rails::GemBoot.parse_gem_version(text) - end -end diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb deleted file mode 100644 index 92132be992..0000000000 --- a/railties/test/gem_dependency_test.rb +++ /dev/null @@ -1,220 +0,0 @@ -require 'plugin_test_helper' -require 'rails/gem_dependency' - -class Rails::GemDependency - public :install_command, :unpack_command -end - -Rails::VendorGemSourceIndex.silence_spec_warnings = true - -class GemDependencyTest < Test::Unit::TestCase - def setup - @gem = Rails::GemDependency.new "xhpricotx" - @gem_with_source = Rails::GemDependency.new "xhpricotx", :source => "http://code.whytheluckystiff.net" - @gem_with_version = Rails::GemDependency.new "xhpricotx", :version => "= 0.6" - @gem_with_lib = Rails::GemDependency.new "xaws-s3x", :lib => "aws/s3" - @gem_without_load = Rails::GemDependency.new "xhpricotx", :lib => false - end - - def test_configuration_adds_gem_dependency - config = Rails::Configuration.new - config.gem "xaws-s3x", :lib => "aws/s3", :version => "0.4.0" - assert_equal [["install", "xaws-s3x", "--version", '"= 0.4.0"']], config.gems.collect { |g| g.install_command } - end - - def test_gem_creates_install_command - assert_equal %w(install xhpricotx), @gem.install_command - end - - def test_gem_with_source_creates_install_command - assert_equal %w(install xhpricotx --source http://code.whytheluckystiff.net), @gem_with_source.install_command - end - - def test_gem_with_version_creates_install_command - assert_equal ["install", "xhpricotx", "--version", '"= 0.6"'], @gem_with_version.install_command - end - - def test_gem_creates_unpack_command - assert_equal %w(unpack xhpricotx), @gem.unpack_command - end - - def test_gem_with_version_unpack_install_command - # stub out specification method, or else test will fail if hpricot 0.6 isn't installed - mock_spec = mock() - mock_spec.stubs(:version).returns('0.6') - @gem_with_version.stubs(:specification).returns(mock_spec) - assert_equal ["unpack", "xhpricotx", "--version", '= 0.6'], @gem_with_version.unpack_command - end - - def test_gem_adds_load_paths - @gem.expects(:gem).with(@gem) - @gem.add_load_paths - end - - def test_gem_with_version_adds_load_paths - @gem_with_version.expects(:gem).with(@gem_with_version) - @gem_with_version.add_load_paths - assert @gem_with_version.load_paths_added? - end - - def test_gem_loading - @gem.expects(:gem).with(@gem) - @gem.expects(:require).with(@gem.name) - @gem.add_load_paths - @gem.load - assert @gem.loaded? - end - - def test_gem_with_lib_loading - @gem_with_lib.expects(:gem).with(@gem_with_lib) - @gem_with_lib.expects(:require).with(@gem_with_lib.lib) - @gem_with_lib.add_load_paths - @gem_with_lib.load - assert @gem_with_lib.loaded? - end - - def test_gem_without_lib_loading - @gem_without_load.expects(:gem).with(@gem_without_load) - @gem_without_load.expects(:require).with(@gem_without_load.lib).never - @gem_without_load.add_load_paths - @gem_without_load.load - end - - def test_gem_dependencies_compare_for_uniq - gem1 = Rails::GemDependency.new "gem1" - gem1a = Rails::GemDependency.new "gem1" - gem2 = Rails::GemDependency.new "gem2" - gem2a = Rails::GemDependency.new "gem2" - gem3 = Rails::GemDependency.new "gem2", :version => ">=0.1" - gem3a = Rails::GemDependency.new "gem2", :version => ">=0.1" - gem4 = Rails::GemDependency.new "gem3" - gems = [gem1, gem2, gem1a, gem3, gem2a, gem4, gem3a, gem2, gem4] - assert_equal 4, gems.uniq.size - end - - def test_gem_load_frozen - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_A_VERSION - end - - def test_gem_load_frozen_specific_version - dummy_gem = Rails::GemDependency.new "dummy-gem-b", :version => '0.4.0' - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_B_VERSION - assert_equal '0.4.0', DUMMY_GEM_B_VERSION - end - - def test_gem_load_frozen_minimum_version - dummy_gem = Rails::GemDependency.new "dummy-gem-c", :version => '>=0.5.0' - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_C_VERSION - assert_equal '0.6.0', DUMMY_GEM_C_VERSION - end - - def test_gem_load_missing_specification - dummy_gem = Rails::GemDependency.new "dummy-gem-d" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_D_VERSION - assert_equal '1.0.0', DUMMY_GEM_D_VERSION - assert_equal ['lib', 'lib/dummy-gem-d.rb'], dummy_gem.specification.files - end - - def test_gem_load_bad_specification - dummy_gem = Rails::GemDependency.new "dummy-gem-e", :version => "= 1.0.0" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_E_VERSION - assert_equal '1.0.0', DUMMY_GEM_E_VERSION - end - - def test_gem_handle_missing_dependencies - dummy_gem = Rails::GemDependency.new "dummy-gem-g" - dummy_gem.add_load_paths - dummy_gem.load - assert_equal 1, dummy_gem.dependencies.size - assert_equal 1, dummy_gem.dependencies.first.dependencies.size - assert_nothing_raised do - dummy_gem.dependencies.each do |g| - g.dependencies - end - end - end - - def test_gem_ignores_development_dependencies - dummy_gem = Rails::GemDependency.new "dummy-gem-k" - dummy_gem.add_load_paths - dummy_gem.load - assert_equal 1, dummy_gem.dependencies.size - end - - def test_gem_guards_against_duplicate_unpacks - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.stubs(:frozen?).returns(true) - dummy_gem.expects(:unpack_base).never - dummy_gem.unpack - end - - def test_gem_does_not_unpack_framework_gems - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.stubs(:framework_gem?).returns(true) - dummy_gem.expects(:unpack_base).never - dummy_gem.unpack - end - - def test_gem_from_directory_name_attempts_to_load_specification - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1') - end - end - - def test_gem_from_directory_name - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1', false) - assert_equal 'dummy-gem', dummy_gem.name - assert_equal '= 1.1', dummy_gem.version_requirements.to_s - end - - def test_gem_from_directory_name_loads_specification_successfully - assert_nothing_raised do - dummy_gem = Rails::GemDependency.from_directory_name(File.join(Rails::GemDependency.unpacked_path, 'dummy-gem-g-1.0.0')) - assert_not_nil dummy_gem.specification - end - end - - def test_gem_from_invalid_directory_name - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem') - end - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy') - end - end - - def test_gem_determines_build_status - assert_equal true, Rails::GemDependency.new("dummy-gem-a").built? - assert_equal true, Rails::GemDependency.new("dummy-gem-i").built? - assert_equal false, Rails::GemDependency.new("dummy-gem-j").built? - end - - def test_gem_determines_build_status_only_on_vendor_gems - framework_gem = Rails::GemDependency.new('dummy-framework-gem') - framework_gem.stubs(:framework_gem?).returns(true) # already loaded - framework_gem.stubs(:vendor_rails?).returns(false) # but not in vendor/rails - framework_gem.stubs(:vendor_gem?).returns(false) # and not in vendor/gems - framework_gem.add_load_paths # freeze framework gem early - assert framework_gem.built? - end - - def test_gem_build_passes_options_to_dependencies - start_gem = Rails::GemDependency.new("dummy-gem-g") - dep_gem = Rails::GemDependency.new("dummy-gem-f") - start_gem.stubs(:dependencies).returns([dep_gem]) - dep_gem.expects(:build).with({ :force => true }).once - start_gem.build(:force => true) - end - -end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index adc61f6d8a..199b5fa8b4 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -89,7 +89,7 @@ class ActionsTest < GeneratorsTestCase def test_environment_should_include_data_in_environment_initializer_block run_generator - load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]' + load_paths = 'config.load_paths += %w["#{Rails.root}/app/extras"]' action :environment, load_paths assert_file 'config/application.rb', /#{Regexp.escape(load_paths)}/ end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5d6a9f6de9..20f2a24e6d 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -114,11 +114,18 @@ class AppGeneratorTest < GeneratorsTestCase generator(:freeze => true, :database => "sqlite3").expects(:run). with("rake rails:freeze:edge", :verbose => false) silence(:stdout){ generator.invoke } - assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/ + + assert_file 'Gemfile' do |content| + flag = %(gem "rails", "#{Rails::VERSION::STRING}", :git => "git://github.com/rails/rails.git") + assert_match /^#{Regexp.escape(flag)}$/, content + + flag = %(# gem "rails", "#{Rails::VERSION::STRING}") + assert_match /^#{Regexp.escape(flag)}$/, content + end end def test_template_from_dir_pwd - FileUtils.cd(RAILS_ROOT) + FileUtils.cd(Rails.root) assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"]) end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 7599bda8a2..ccf08c347c 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -1,24 +1,10 @@ -require 'test/unit' -require 'fileutils' - -fixtures = File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures')) -if defined?(RAILS_ROOT) - RAILS_ROOT.replace fixtures -else - RAILS_ROOT = fixtures -end - -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activemodel/lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" # TODO: Fix this RAILS_ENV stuff RAILS_ENV = 'test' -require "rails/core" -require 'rails/generators' +require 'abstract_unit' +Rails.application.config.root = File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures')) +require 'rails/generators' require 'rubygems' - require 'active_record' require 'action_dispatch' diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index f555725eb8..02155c295c 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -2,6 +2,11 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +module Unknown + module Generators + end +end + class ScaffoldControllerGeneratorTest < GeneratorsTestCase def test_controller_skeleton_is_created @@ -97,10 +102,38 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase assert_no_file "app/views/layouts/users.html.erb" end - def test_error_is_shown_if_orm_does_not_provide_interface - error = capture(:stderr){ run_generator ["User", "--orm=unknown"] } - assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " << - "Error: no such file to load -- rails/generators/unknown.\n", error + def test_default_orm_is_used + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match /class UsersController < ApplicationController/, content + + assert_instance_method content, :index do |m| + assert_match /@users = User\.all/, m + end + end + end + + def test_customized_orm_is_used + klass = Class.new(Rails::Generators::ActiveModel) do + def self.all(klass) + "#{klass}.find(:all)" + end + end + + Unknown::Generators.const_set(:ActiveModel, klass) + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match /class UsersController < ApplicationController/, content + + assert_instance_method content, :index do |m| + assert_match /@users = User\.find\(:all\)/, m + assert_no_match /@users = User\.all/, m + end + end + ensure + Unknown::Generators.send :remove_const, :ActiveModel end protected diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 7e6b7b183c..178b5ef6de 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -80,7 +80,7 @@ class GeneratorsTest < GeneratorsTestCase Rails::Generators.instance_variable_set(:@load_paths, nil) spec = Gem::Specification.new - spec.expects(:full_gem_path).returns(File.join(RAILS_ROOT, 'vendor', 'another_gem_path', 'xspec')) + spec.expects(:full_gem_path).returns(File.join(Rails.root, 'vendor', 'another_gem_path', 'xspec')) Gem.expects(:respond_to?).with(:loaded_specs).returns(true) Gem.expects(:loaded_specs).returns(:spec => spec) @@ -101,10 +101,11 @@ class GeneratorsTest < GeneratorsTestCase def test_rails_generators_with_others_information output = capture(:stdout){ Rails::Generators.help }.split("\n").last - assert_equal "Others: active_record:fixjour, fixjour, mspec, rails:javascripts, wrong.", output + assert_equal "Others: active_record:fixjour, fixjour, mspec, rails:javascripts.", output end def test_warning_is_shown_if_generator_cant_be_loaded + Rails::Generators.load_paths << File.expand_path("../fixtures/vendor/gems/gems/wrong", __FILE__) output = capture(:stderr){ Rails::Generators.find_by_namespace(:wrong) } assert_match /\[WARNING\] Could not load generator at/, output assert_match /Error: uninitialized constant Rails::Generator/, output @@ -118,7 +119,7 @@ class GeneratorsTest < GeneratorsTestCase end def test_rails_root_templates - template = File.join(RAILS_ROOT, "lib", "templates", "active_record", "model", "model.rb") + template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb") # Create template mkdir_p(File.dirname(template)) @@ -170,6 +171,6 @@ class GeneratorsTest < GeneratorsTestCase def test_source_paths_for_not_namespaced_generators mspec = Rails::Generators.find_by_namespace :mspec - assert mspec.source_paths.include?(File.join(RAILS_ROOT, "lib", "templates", "mspec")) + assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "mspec")) end end diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index 7c8aed00c9..f7237e69cc 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -31,38 +31,113 @@ module InitializableTests end end + class Parent + extend Rails::Initializable + + initializer :one do + $arr << 1 + end + + initializer :two do + $arr << 2 + end + end + + class Child < Parent + extend Rails::Initializable + + initializer :three, :before => :one do + $arr << 3 + end + + initializer :four, :after => :one do + $arr << 4 + end + end + + class Parent + initializer :five, :before => :one do + $arr << 5 + end + end + + class Instance + include Rails::Initializable + + initializer :one do + $arr << 1 + end + + initializer :two do + $arr << 2 + end + + initializer :three, :global => true do + $arr << 3 + end + + initializer :four, :global => true do + $arr << 4 + end + end + class Basic < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation test "initializers run" do - Foo.initializers.run + Foo.run_initializers assert_equal 1, Foo.foo end test "initializers are inherited" do - Bar.initializers.run + Bar.run_initializers assert_equal [1, 1], [Bar.foo, Bar.bar] end test "initializers only get run once" do - Foo.initializers.run - Foo.initializers.run + Foo.run_initializers + Foo.run_initializers assert_equal 1, Foo.foo end test "running initializers on children does not effect the parent" do - Bar.initializers.run + Bar.run_initializers assert_nil Foo.foo assert_nil Foo.bar end - test "inherited initializers are the same objects" do - assert Foo.initializers[:foo].eql?(Bar.initializers[:foo]) - end - test "initializing with modules" do - Word.initializers.run + Word.run_initializers assert_equal "bird", $word end end + + class BeforeAfter < ActiveSupport::TestCase + test "running on parent" do + $arr = [] + Parent.run_initializers + assert_equal [5, 1, 2], $arr + end + + test "running on child" do + $arr = [] + Child.run_initializers + assert_equal [5, 3, 1, 4, 2], $arr + end + end + + class InstanceTest < ActiveSupport::TestCase + test "running locals" do + $arr = [] + instance = Instance.new + instance.run_initializers + assert_equal [1, 2], $arr + end + + test "running globals" do + $arr = [] + Instance.run_initializers + assert_equal [3, 4], $arr + end + end end
\ No newline at end of file diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb index 6c32f5635b..cf956e68fb 100644 --- a/railties/test/initializer/check_ruby_version_test.rb +++ b/railties/test/initializer/check_ruby_version_test.rb @@ -7,6 +7,7 @@ module InitializerTests def setup build_app boot_rails + require "rails" end test "rails does not initialize with ruby version 1.8.1" do @@ -42,7 +43,7 @@ module InitializerTests set_ruby_version(version) assert_nothing_raised "It appears that rails does not boot" do Rails::Initializer.run { |c| c.frameworks = [] } - Rails.application.new + Rails.initialize! end end @@ -51,7 +52,7 @@ module InitializerTests $stderr = File.open("/dev/null", "w") assert_raises(SystemExit) do Rails::Initializer.run { |c| c.frameworks = [] } - Rails.application.new + Rails.initialize! end end end diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb index f952d06f94..d664f96ad1 100644 --- a/railties/test/initializer/initialize_i18n_test.rb +++ b/railties/test/initializer/initialize_i18n_test.rb @@ -7,14 +7,16 @@ 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.application.new + Rails.initialize! #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml assert_equal %W( @@ -24,7 +26,7 @@ module InitializerTests #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/railties/tmp/app/config/locales/en.yml my/other/locale.yml - ), I18n.load_path + ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } end test "i18n finds locale files in engines" do @@ -34,9 +36,10 @@ module InitializerTests app_file "vendor/plugins/engine/config/locales/en.yml", "hello:" Rails::Initializer.run do |c| + c.root = app_path c.i18n.load_path << "my/other/locale.yml" end - Rails.application.new + Rails.initialize! #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml assert_equal %W( @@ -47,7 +50,7 @@ module InitializerTests #{app_path}/config/locales/en.yml my/other/locale.yml #{app_path}/vendor/plugins/engine/config/locales/en.yml - ), I18n.load_path + ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } end end end
\ No newline at end of file diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb index ce8fc4b8b0..1b58a58555 100644 --- a/railties/test/initializer/path_test.rb +++ b/railties/test/initializer/path_test.rb @@ -6,13 +6,15 @@ class PathsTest < Test::Unit::TestCase def setup build_app boot_rails + require "rails" Rails::Initializer.run do |config| + 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.application.new + Rails.initialize! @paths = Rails.application.config.paths end diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb deleted file mode 100644 index 80e774b7b7..0000000000 --- a/railties/test/initializer_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'abstract_unit' -require 'rails/initializer' -require 'rails/generators' - -require 'action_view' -require 'action_mailer' -require 'active_record' - -require 'plugin_test_helper' - -class RailsRootTest < Test::Unit::TestCase - def test_rails_dot_root_equals_rails_root - assert_equal RAILS_ROOT, Rails.root.to_s - end - - def test_rails_dot_root_should_be_a_pathname - assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root.join('app', 'controllers').to_s - end -end - diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 5bc878b3be..462a4d8dea 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -6,7 +6,6 @@ # # It is also good to know what is the bare minimum to get # Rails booted up. - require 'fileutils' # TODO: Remove rubygems when possible @@ -93,6 +92,12 @@ module TestHelpers add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' end + def script(script) + Dir.chdir(app_path) do + `#{Gem.ruby} #{app_path}/script/#{script}` + end + end + def add_to_config(str) environment = File.read("#{app_path}/config/application.rb") if environment =~ /(\n\s*end\s*)\Z/ @@ -114,16 +119,23 @@ module TestHelpers end def boot_rails - # TMP mega hax to prevent boot.rb from actually booting - Object.class_eval <<-RUBY, __FILE__, __LINE__+1 - module Rails - Initializer = 'lol' - require "#{app_path}/config/boot" - remove_const(:Initializer) - booter = VendorBoot.new - booter.run + root = File.expand_path('../../../..', __FILE__) + begin + require "#{root}/vendor/gems/environment" + rescue LoadError + %w( + actionmailer/lib + actionpack/lib + activemodel/lib + activerecord/lib + activeresource/lib + activesupport/lib + railties/lib + railties + ).reverse_each do |path| + $:.unshift "#{root}/#{path}" end - RUBY + end end end end @@ -142,7 +154,12 @@ Module.new do if File.exist?(tmp_path) FileUtils.rm_rf(tmp_path) end - FileUtils.mkdir(tmp_path) - `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}` + + environment = File.expand_path('../../../../vendor/gems/environment', __FILE__) + + `#{Gem.ruby} -r #{environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}` + File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f| + f.puts "require '#{environment}' ; require 'rails'" + end end diff --git a/railties/test/plugins/vendored_test.rb b/railties/test/plugins/vendored_test.rb new file mode 100644 index 0000000000..71de542ff7 --- /dev/null +++ b/railties/test/plugins/vendored_test.rb @@ -0,0 +1,19 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class PluginTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + test "generates the plugin" do + script "generate plugin my_plugin" + File.open("#{app_path}/vendor/plugins/my_plugin/init.rb", 'w') do |f| + f.puts "OMG = 'hello'" + end + require "#{app_path}/config/environment" + 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 99cf9168e1..a0484c0868 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -4,10 +4,6 @@ require 'action_controller' require 'rails/info' require 'rails/info_controller' -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end - module ActionController class Base include ActionController::Testing @@ -18,9 +14,17 @@ 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' + 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 |